Out-of-bounds read information disclosure vulnerability in Microsoft Windows GDI+ EMR_CREATEDIBPATTERNBRUSHPT record
This article, the third 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_CREATEDIBPATTERNBRUSHPT
enhanced metafile record. For other articles in the series click here.
TL;DR
An information disclosure vulnerability (CVE-2022-26934) 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_CREATEDIBPATTERNBRUSHPT
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.1110 of gdi32full.dll
and version 10.0.19041.1348 of gdiplus.dll
.
The below is the exception output and the relevant excerpt of the call stack from WinDbg after a crash detected in the MfEnumState::ModifyDib()
function.
10:000> g
2(2138.16ec): Access violation - code c0000005 (first chance)
3First chance exceptions are reported before any exception handling.
4This exception may be expected and handled.
5eax=081db073 ebx=00000002 ecx=0000001d edx=00000000 esi=081dafff edi=082d28a0
6eip=75ff8d4a esp=012ff0e8 ebp=012ff0f0 iopl=0 nv up ei pl nz na pe nc
7cs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00010206
8msvcrt!memcpy+0x5a:
975ff8d4a f3a5 rep movs dword ptr es:[edi],dword ptr [esi]
100:000> kb
11 # ChildEBP RetAddr Args to Child
1200 012ff0f0 6eb1592c 082d27f0 081daf4f 00000124 msvcrt!memcpy+0x5a
1301 012ff124 6eac784e 00000001 081daf23 00000000 gdiplus!MfEnumState::ModifyDib+0x6e
1402 012ff164 6eb13bfb 081daf23 00000000 012ff194 gdiplus!EmfEnumState::CreateModifiedDib+0x61920
1503 012ff198 6eac7608 082d2720 6ea65140 0000005e gdiplus!EmfEnumState::CreateDibPatternBrushPt+0x126
1604 012ff1ac 6ea889dd 0000005e 00000184 081dae64 gdiplus!EmfEnumState::ProcessRecord+0x624c8
1705 012ff1d0 6eaa8ecc 0000005e 00000000 00000184 gdiplus!GdipPlayMetafileRecordCallback+0xdd
1806 012ff1fc 75c54b25 cc011116 081dc800 081dae5c gdiplus!EnumEmfDownLevel+0x6c
1907 012ff2d0 75c4dd2c 6eaa8e60 082cfcb0 012ff344 gdi32full!bInternalPlayEMF+0x855
2008 012ff2e4 76b6462b cc011116 1c4610b6 6eaa8e60 gdi32full!EnumEnhMetaFile+0x2c
2109 012ff304 6ea69302 cc011116 1c4610b6 6eaa8e60 GDI32!EnumEnhMetaFileStub+0x2b
220a 012ff358 6ea6680c cc011116 1c4610b6 012ff3fc gdiplus!MetafilePlayer::EnumerateEmfRecords+0xc8
230b 012ff410 6ea6addc 082cfcb0 1c4610b6 012ff558 gdiplus!GpGraphics::EnumEmf+0x464
240c 012ff580 6ea781e1 012ff5c4 012ff5c4 00000002 gdiplus!GpMetafile::EnumerateForPlayback+0x651
250d 012ff6d8 6ea893ff 08295f28 012ff708 012ff718 gdiplus!GpGraphics::DrawImage+0x541
260e 012ff744 6eaf9898 08295f28 012ff76c 012ff77c gdiplus!GpGraphics::DrawImage+0x61
270f 012ff7a4 6eafaaaf 00000064 00000064 00000064 gdiplus!GpMetafile::GetBitmap+0x1c0
2810 012ff7b8 6eadea95 00000064 00000064 0829bff0 gdiplus!GpMetafile::GetThumbnail+0x2f
2911 012ff7e0 002b128b 08295f28 00000064 00000064 gdiplus!GdipGetImageThumbnail+0x65
30...
310:000> !msec.exploitable
32Exploitability Classification: PROBABLY_EXPLOITABLE
33Recommended Bug Title: Probably Exploitable - Read Access Violation on Block Data Move starting at msvcrt!memcpy+0x000000000000005a (Hash=0x4b166071.0x5026552b)
34
35This is a read access violation in a block data move, and is therefore classified as probably exploitable.
Crash analysis
Further analysis revealed that the MfEnumState::ModifyDib()
function will read past the BITMAPINFO
1 structure, due to an invalid biWidth
value that specifies the width of the bitmap, in pixels.
The EMRCREATEDIBPATTERNBRUSHPT
2 structure contains members for the CreateDIBPatternBrushPt
enhanced metafile record, as described in the Windows GDI documentation. The below is the raw EMR
record, that defines a pattern brush for graphics operations, starting at offset 6Ch
in the crash sample:
1006Ch: 5E 00 00 00 8C 01 00 00 64 00 00 00 01 00 00 00 ^...Œ...d.......
2007Ch: C7 00 00 00 BF 00 00 00 64 00 00 00 64 00 00 00 Ç...¿...d...d...
The following is the human readable representation of the above EMRCREATEDIBPATTERNBRUSHPT
record:
1typedef struct tagEMRCREATEDIBPATTERNBRUSHPT {
2 DWORD iType = 0x5e; // EMRCREATEDIBPATTERNBRUSHPT
3 DWORD nSize = 0x18c; // Size of the record, in bytes.
4 DWORD ihBrush = 0x64; // Index of brush in handle table.
5 DWORD iUsage = 0x1; // DIB_PAL_COLORS
6 DWORD offBmi = 0xc7; // Offset from the start of this record to the DIB header.
7 DWORD cbBmi = 0xbf; // Size of the DIB header.
8 DWORD offBits = 0x64; // Offset to bitmap bits.
9 DWORD cbBits = 0x64; // Size of bitmap bits.
10} EMRCREATEDIBPATTERNBRUSHPT;
DIB_PAL_COLORS
means that the color table consists of an array of 16-bit indexes into the LogPalette
object that is currently defined in the playback device context.
The BITMAPINFOHEADER
3 structure contains information about the dimensions and color format of a DIB
. The BITMAPINFO
structure is followed by the bitmap bits that form a packed DIB
The below is the raw structure that contains information about the dimensions and color format of a DIB
, starting at offset 133h
in the provided crash sample:
10133h: 28 00 00 00 01 09 00 00 01 00 00 00 01 00 01 00 (...............
20143h: 00 00 00 00 04 00 00 00 00 00 00 00 00 00 00 00 ................
30153h: 00 00 00 00 00 00 00 00 ........
The following is the human readable representation of the above BITMAPINFOHEADER
structure:
1typedef struct tagBITMAPINFOHEADER {
2 DWORD biSize = 0x28;
3 LONG biWidth = 0x901; // Width of the bitmap, in pixels.
4 LONG biHeight = 0x1;
5 WORD biPlanes = 0x1;
6 WORD biBitCount = 0x1;
7 DWORD biCompression = 0x0; // BI_RGB, uncompressed.
8 DWORD biSizeImage = 0x4; // Size of the image, in bytes.
9 LONG biXPelsPerMeter = 0x0;
10 LONG biYPelsPerMeter = 0x0;
11 DWORD biClrUsed = 0x0;
12 DWORD biClrImportant = 0x0;
13} BITMAPINFOHEADER;
In the attached crash sample, MfEnumState::ModifyDib()
will call memcpy()
with a 0x124
passed as size. This value depends on the size of the DIB
and it is calculated from the biWidth
member of the BITMAPINFOHEADER
by the GetDibBitsSize()
function which returns 0
, if there are any problems detected in the headers, including invalid values in specific fields (biWidth
, biHeight
, …).
1int GetDibBitsSize(BITMAPINFOHEADER *pbmi, DWORD *biSizeImage) {
2 lSrcStride = 0;
3 if ( pbmi->biSize >= 0x28 && pbmi->biWidth > 0 && pbmi->biPlanes == 1 )
4 {
5 biCompression = pbmi->biCompression;
6 if ( biCompression && biCompression != BI_BITFIELDS && biCompression != 10 )
7 {
8 *biSizeImage = pbmi->biSizeImage;
9 return 1;
10 }
11 biHeight = pbmi->biHeight;
12 if ( biHeight < 0 )
13 biHeight = -biHeight;
14 if ( GetDibByteWidth(pbmi->biWidth, 1, pbmi->biBitCount, &lSrcStride) >= 0
15 && UIntMult(lSrcStride, biHeight, &biSize, v6, nSize) >= 0 )
16 {
17 *biSizeImage = biSize; // 0x124
18 return 1;
19 }
20 }
21 *biSizeImage = 0;
22 return 0;
23}
Root cause analysis
The GetDibByteWidth()
function calculates the stride of the image. The stride
is the number of bytes from one row of pixels in memory to the next row of pixels in memory. If padding bytes are present, the stride is wider than the width of the image. For uncompressed RGB formats as specified by biCompression = 0x0
, the minimum stride is always the image width in bytes, rounded up to the nearest DWORD
, according to the following formula:
1((((biWidth * biBitCount) + 0x1F) & 0x1FFFFFFC) >> 0x3) = stride
Substituting the proper values into the above formula will give us the value of the size parameter of the memcpy()
call that triggers the access violation.
1((((0x901 * 0x1) + 0x1F) & 0x1FFFFFFC) >> 0x3) = 0x124
The GetBitmapFromRecord()
function ensures that the EMF record is sufficiently large to fully contain the bitmap data, and is called before any bitmap parsing actually takes place in the CreateDibPatternBrushPt()
record handler function.
1int GetBitmapFromRecord(...)
2{
3 result = 0;
4 if (nSize >= 0x2C && offBmi <= nSize - 0x2C && offBits <= nSize)
5 {
6 ...
7 if (GetDibNumPalEntries(0, *&offBmi[cbBits], biBitCount, biCompression, v12, &biSize)) // biSize = 0x2
8 {
9 if ( UIntMult(4u, biSize, &cbBits, v13, v15) >= 0 // cbBits = 0x8
10 && SizeTAdd(cbBits, **pbmi, &cbBits, v14, v16) >= 0 // cbBits = 0x30
11 && cbBits <= nSize - offBmi // 0x30 <= 0x18C - 0xC7 -> OK
12 && GetDibBitsSize(*pbmi, &biSize) // biSize = 0x124
13 && nSize - offBmi >= **pbmi // 0x18C - 0xC7 >= 0x28 -> OK
14 && nSize - offBits >= biSize ) // 0x18C - 0x64 >= 0x124 -> OK
15 {
16 result = 1;
17 }
18 }
19 }
20 return result;
21}
However, the EmfEnumState::CreateModifiedDib()
function does not sufficiently check that the source bitmap is valid before calling the MfEnumState::ModifyDib()
function which performs a bit-block transfer of the color data and copies the source rectangle directly to the destination rectangle as specified by the SRCCOPY
raster-operation code.
1int EmfEnumState::CreateModifiedDib(...) {
2 result = 0;
3 if ( Src->biSize < 0x28
4 || !GetDibNumPalEntries(0, Src->biSize, Src->biBitCount, Src->biCompression, Src->biClrUsed, &biSize)
5 || !GetDibBitsSize(Src, &Size)
6 || Size <= 0 )
7 {
8 return result;
9 }
10
11 if ( biSize != 2 || rop == SRCCOPY || *(&Src->biSize + 0x28) || *(&Src->biWidth + 0x28) != 0xFFFFFF )
12 {
13 v9 = *a4;
14 dstSize = MfEnumState::GetModifiedDibSize(Src, biSize, Size, a4);
15 if ( dstSize > 0 )
16 {
17 if ( MfEnumState::CreateRecordToModify(dstSize) )
18 {
19 Dst = *(this + 0x98);
20 MfEnumState::ModifyDib(this, Src, v9, Src, a3, Dst, biSize, Size, 1); // Crash here!
21 }
22 }
23 return Dst;
24 }
25
26 return 0;
27}
The MfEnumState::ModifyDib()
function will try to copy 292
bytes, however, the real size of our buffer is only 176
bytes, hence the memcpy()
function will copy additional 29
bytes past the buffer. The crash can be only reproduced with PageHeap
enabled, otherwise execution continues with the record handler function CreateDIBPatternBrushPt()
.
Patch analysis
We already know the root cause, however, we do not know how this issue was fixed in version 10.0.19041.1706 of gdiplus.dll
. Diffing the patched and the vulnerable DLLs using the BinDiff plugin available for IDA Pro, Binary Ninja or Ghidra, we can identify what changes have been made to the patched file.
The Matched Functions
subview displays the pairs of functions that are associated with each other. The EmfEnumState::CreateDibPatternBrushPt()
function shows only 94% similarity with 99% confidence. Let’s examine the changes in this function in the BinDiff graph GUI. Note that the grey nodes indicate basic blocks containing newly added code.
The vulnerability was fixed by merging GetBitmapFromRecord()
into the EmfEnumState::CreateDibPatternBrushPt()
function and calling the IsValidBitmapRecordSize()
function at 0x1007e698
to verify the size of the record before moving on to EmfEnumState::CreateModifiedDib()
, as shown by the below pseudocode:
1HBRUSH EmfEnumState::CreateDibPatternBrushPt()
2{
3 if ( GetDibNumPalEntries(...) )
4 {
5 if ( GetDibBitsSize(...) )
6 {
7 if ( a4 )
8 {
9 ...
10 if ( IsValidBitmapRecordSize(...) )
11 {
12 lpPackedDIB = EmfEnumState::CreateModifiedDib(Src, 0, &iUsage, SRCCOPY);
13 if ( lpPackedDIB )
14 {
15 return CreateDIBPatternBrushPt(lpPackedDIB, iUsage);
16 }
17 }
18 }
19 }
20 }
21}
Timeline
⬅️ 2021-12-29: Reported issue to MSRC.
➡️ 2021-12-29: MSRC opened case 69345.
➡️ 2022-01-13: MSRC confirmed the vulnerability.
➡️ 2022-05-10: MSRC assigned CVE-2022-26934.
➡️ 2022-05-10: Coordinated public release of advisory.
Bibliography
- CVE-2022-26934 - Security Update Guide - Microsoft - Windows GDI+ Information Disclosure Vulnerability
- ZDI-19-555: Microsoft Windows gdiplus EMR_CREATEDIBPATTERNBRUSHPT Out-Of-Bounds Read Information Disclosure Vulnerability
- Adobe Acrobat Pro DC ImageConversion EMF EMR_CREATEDIBPATTERNBRUSHPT Heap-based Buffer Overflow Remote Code Execution Vulnerability
- Corel CorelDRAW X8 EMF Parser Code Execution Vulnerability
- Microsoft GDI WMF Parsing Heap Overflow Vulnerability
- Time Travel Debugging - Sample App Walkthrough