CATEGORIES

Drawn to Danger: Windows Graphics Vulnerabilities Lead to Remote Code Execution and Memory Exposure

November 2, 2025

Background

Check Point Research (CPR) identified three security vulnerabilities in the Graphics Device Interface (GDI) in Windows. We promptly reported these issues to Microsoft, and they were addressed in the Patch Tuesday updates in May, July, and August 2025.

These are the vulnerabilities:

  • CVE-2025-30388, rated important and considered more likely to be exploited;
  • CVE-2025-53766, classified as critical severity and may allow remote attackers to execute arbitrary code on affected systems;
  • CVE-2025-47984, also rated important and can result in the unauthorized disclosure of sensitive information over the network.

Vulnerability disclosures such as these highlight the need for proactive measures to mitigate potential risks. Our purpose in publishing this blog after security fixes were implemented is to further raise awareness of these vulnerabilities and provide Windows users with defensive insights and mitigation recommendations. In the following sections, we detail the findings of our fuzzing campaign, which targeted Windows GDI using the EMF format and led to the discovery of these security vulnerabilities.

Geometry Gone Rogue – CVE-2025-30388

We found three separate crashes related to the processing of EmfPlusDrawStringEmfPlusFillRects and EmfPlusFillClosedCurve records. All three cases have a common root cause: another record sets the stage for exploitation. However, the outcome varies depending on which additional records are processed during the execution. Our current analysis focuses on the crash involving the EmfPlusDrawString record.

Multiple access violation exceptions occurred in the ScanOperation::AlphaMultiply_sRGB()ScanOperation::Blend_sRGB_sRGB_MMX() and EpAntialiasedFiller::OutputSpan() functions within version 10.0.26100.3037 of the GdiPlus.dll module. These exceptions were triggered when the system attempted to read or write memory at the end of a 4000/0xFA0 bytes heap block, or while attempting to access reserved but unallocated memory.

This vulnerability could potentially allow a remote attacker to perform out-of-bounds read or write memory operations using a specially crafted EMF+ metafile. Figure 1 shows the decompiled source code of the ScanOperation::AlphaMultiply_sRGB() function at the time of the crash.

Figure 1. Decompiled source code of the affected ScanOperation::AlphaMultiply_sRGB() function.

In our crash sample, which serves as a proof of concept (PoC) for reproducing a vulnerability, an EmfPlusClear record is located before the EmfPlusDrawString record within the metafile. This record clears the output coordinate space and initializes it with a background color and transparency, as defined by its Color field. The field contains an EmfPlusARGB object specifying red, green, blue, and alpha components. This detail is significant because it allows an attacker to control the value written to memory during exploitation. Listing 1 shows the affected EmfPlusClear record.

EmfPlusClear clear = {
    .Type     = 0x4009,
    .Flags    = 0x0102,
    .Size     = 0x0000003c,
    .DataSize = 0x00000030,
    .Color    = 0xaabbccff  // Value written to memory
};

Listing 1. Sample EmfPlusClear record showing the value written to memory.

Further investigation revealed that the EmfPlusClear record handler uses the EpScanBitmap::Start() function to allocate a heap block to store 4000 bytes (0xFA0). This buffer is then populated with the specified EmfPlusARGB object, which undergoes alpha multiplication by the AlphaMultiply_sRGB() function during the processing of the EmfPlusDrawString record.

Note that the loop counter stored in the ebx register begins at 0x950. As each iteration writes a 4-byte object into the target buffer at ecx + edx, the function writes out-of-bounds after 1000 bytes, when the counter reaches 0x567. This behavior can be observed in Listing 2 which shows an excerpt from the crash analysis.

0:000> g
(16b8.5ec): Access violation - code c0000005 (first/second chance not available)
First chance exceptions are reported before any exception handling.
This exception may be expected and handled.
eax=ffccbbaa ebx=00000567 ecx=022cec8c edx=08732374 esi=000015e3 edi=db000000
eip=74e16d9d esp=0075ddb0 ebp=0075ddc0 iopl=0         nv up ei ng nz na po nc
cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00000282
gdiplus!ScanOperation::AlphaMultiply_sRGB+0x2d:
74e16d9d 890411          mov     dword ptr [ecx+edx],eax ds:002b:0aa01000=????????
0:000> kb
 # ChildEBP RetAddr      Args to Child              
00 0075ddc0 74e16653     00000950 0872bd0c 0075e99c gdiplus!ScanOperation::AlphaMultiply_sRGB+0x2d
01 0075ddf4 74e16520     00000000 00000000 00000015 gdiplus!EpScanBitmap::NextBuffer+0xc3
02 0075de2c 74e15c91     00000000 00000000 00000015 gdiplus!DpOutputSolidColorSpan::OutputSpan+0x40
03 0075de58 74e188d4     00000000 ffffff19 00000955 gdiplus!DpClipRegion::OutputSpan+0x141
04 0075de7c 74e15988     00000000 0f47eed0 74e01b40 gdiplus!EpAliasedFiller::FillEdgesWinding+0x54
05 0075e938 74e0420e     00000001 00000003 00000000 gdiplus!RasterizePath+0x5d8
06 0075ea74 74e01cee     08747da0 0873cf98 0075eab8 gdiplus!DpDriver::StrokePath+0x3ee
07 0075ea9c 74e01db7     0075eab8 0075eb20 0075ede0 gdiplus!GpGraphics::DrvStrokePath+0x38
08 0075eadc 74df3b06     0075eb10 0075eb20 0075edcc gdiplus!GpGraphics::RenderDrawPath+0xbc
09 0075ed68 74e50a58     0075edcc 0075ed98 00000002 gdiplus!GpGraphics::DrawLines+0x148
0a 0075ee54 74f034c2     0aa08ff0 0f2a2fb8 00000000 gdiplus!FullTextImager::GdipLscbkDrawUnderline+0x228
0b 0075eee4 74ef7d57     0075f048 00000004 00000000 gdiplus!DrawUnderlineMerge+0x16f
0c 0075ef90 74eef9fd     00000001 00000000 00001015 gdiplus!DisplaySublineCore+0x25b
0d 0075f018 74ed907c     0aa40fa0 0aa40fa0 0aa40fa0 gdiplus!LsDisplayLine+0x193
0e 0075f02c 74edc08e     0075f048 00000000 0aa04e70 gdiplus!BuiltLine::Draw+0x12
0f 0075f058 74edbfd4     0aa40fa0 00000000 0aa04e70 gdiplus!FullTextImager::RenderLine+0x50
10 0075f0d8 74ed915f     0aa04e70 74ed9090 00000000 gdiplus!FullTextImager::Render+0x211
11 0075f108 74e6192a     0873ed28 0075f150 08744cb0 gdiplus!FullTextImager::Draw+0xcf
12 0075f3c8 74e8c55f     05b70184 00000014 0874bfe0 gdiplus!GpGraphics::DrawString+0x2f412
13 0075f404 74e02081     08744cb0 0000401c 0000df00 gdiplus!DrawStringEPR::Play+0xdf                        // Affects EmfPlusDrawString record
14 0075f428 74e01f97     0000401c 0000df00 00000044 gdiplus!GdipPlayMetafileRecordCallback+0x71
15 0075f45c 74e01e7c     00000104 05b700a8 0865ad58 gdiplus!MetafilePlayer::EnumerateEmfPlusRecords+0x97
16 0075f47c 75ca5d2f     a901095a 0866cff8 05b70098 gdiplus!EnumEmfWithDownLevel+0x8c
17 0075f510 75ca4309     74e01df0 08744cb0 0075f58c gdi32full!bInternalPlayEMF+0x830
18 0075f524 771cad7f     a901095a 824606f7 74e01df0 gdi32full!EnumEnhMetaFile+0x39
19 0075f544 74df5ed6     a901095a 824606f7 74e01df0 GDI32!EnumEnhMetaFileStub+0x2f
1a 0075f5a0 74df5b3b     a901095a 824606f7 0075f5e4 gdiplus!MetafilePlayer::EnumerateEmfRecords+0xdf
1b 0075f648 74df81bc     08744cb0 824606f7 0075f774 gdiplus!GpGraphics::EnumEmfPlusDual+0x351              // Affects EMF+ Dual
1c 0075f7b8 74e1384c     0075f810 0075f810 00000002 gdiplus!GpMetafile::EnumerateForPlayback+0x819
1d 0075f8d8 74e01903     08723f28 0075f908 0075f918 gdiplus!GpGraphics::DrawImage+0x5ec
1e 0075f944 74e8a69e     08723f28 0075f96c 0075f97c gdiplus!GpGraphics::DrawImage+0x61
1f 0075f9a4 74e8b8a6     00000064 00000064 08723f28 gdiplus!GpMetafile::GetBitmap+0x1d2
20 0075f9b8 74e6e16b     00000064 00000064 00000000 gdiplus!GpMetafile::GetThumbnail+0x26
21 0075f9e0 006511a0     08723f28 00000064 00000064 gdiplus!GdipGetImageThumbnail+0x6b                     // Reachable via thumbnails
<<<<<<<<<<<<<<unnecessary information removed>>>>>>>>>>>>>>
0:000> dd [ecx+edx]-0xfa0-10 L8
0aa00050  00000000 00000000 046e0b3c dcbabbbb
0aa00060  ffccbbaa ffccbbaa ffccbbaa ffccbbaa 
0:000> dd [ecx+edx]-10 L8
0aa00ff0  ffccbbaa ffccbbaa ffccbbaa ffccbbaa 
0aa01000  ???????? ???????? ???????? ????????

Listing 2. Stack trace showing an access violation in the ScanOperation::AlphaMultiply_sRGB() function.

Alpha values are multiplied by color values to ensure that transparent areas (where the alpha value is 0) do not contribute any color (R = G = B = 0). As a result, colors in semi-transparent areas appear darker because these objects emit less light compared to opaque ones.

The fourth byte is the alpha value, which can range from 0x00 to 0xFF. Setting the alpha value to 0xFF allows an attacker near-complete control over what is written to the buffer, as the RGB values remain unchanged. In the example above, B=0xAAG=0xBBR=0xCC and A=0xFF were written into the target buffer.

Figure 2. Binary differences showing the new ValidateAndSet() and IsRectValid() functions.

As illustrated by Figure 2, patch analysis confirmed that the crashes which occurred during the processing of various metafile records all shared a common root cause: the presence of invalid RECT objects within an EmfPlusSetTSClip record that preceded the other records in which the crashes were observed. Listing 3 shows the affected record from our crash samples.

EmfPlusSetTSClip clip = {
    .Type        = 0x403a,
    .Flags       = 0x0003, // Number of RECT objects
    .Size        = 0x00000048,
    .DataSize    = 0x0000003c,
    .NumRects[3] = {
        { 0x00000005, 0x00000000, 0x04000000, 0x0000001f }, // Valid
        { 0x00000000, 0x00e90000, 0xfff70000, 0x00000000 }, // Not valid
        { 0x00000000, 0x00000000, 0x00000015, 0x003f8000 }  // Valid
    }
};

Listing 3. Sample EmfPlusSetTSClip record containing invalid RECT objects.

Processing invalid RECT objects results in a heap-based buffer overflow that may allow an attacker to perform out-of-bounds memory operations. An attacker could exploit this vulnerability by using various other metafile records to write or read memory, as a corrupted EmfPlusSetTSClip record sets the stage for exploitation. Looking at the rendering of the sample EmfPlusFillRects record in Figure 3 indicates that it is possible to disclose uninitialized or initialized heap bytes via the filled rectangle.

Figure 3. Leaking heap memory in Word via the sample EmfPlusFillRects record.

As we can see from the resulting image in Microsoft Word 365, the output varies in each rendering, which demonstrates that this crash sample leaks memory and eventually Word is unexpectedly terminated. This behavior already crosses a security boundary as it can lead to information disclosure if an attacker can read back the rendered image, for example, using JavaScript in a web browser.

Microsoft fixed this vulnerability within the SetTSClipEPR::Play() handler function in version 10.0.26100.4061 of GdiPlus.dll by introducing the ValidateAndSet() and IsRectValid() functions to validate RECT objects, as shown in Figure 4.

<em>Figure 4.</em> New functions in <code>GdiPlus.dll</code> to
validate <code>RECT</code> objects.
Figure 4. New functions in GdiPlus.dll to validate RECT objects.

This bug was addressed with KB5058411 in the May 2025 Patch Tuesday as a remote code execution vulnerability of important severity, tracked as CVE-2025-30388. Notably, this issue also affects Microsoft Office for Mac and Android. In addition, MSRC’s exploitability assessment is “Exploitation More Likely”, indicating that this vulnerability presents a high-value target for attackers.

Negative Space – CVE-2025-53766

We identified a fourth crash while processing an EmfPlusDrawRects record. An access violation exception occurred in the ScanOperation::AlphaDivide_sRGB() function within version 10.0.26100.4202 of GdiPlus.dll, as it attempted to write to reserved but unallocated memory.

This vulnerability could allow a remote attacker to perform an out-of-bounds memory write using a specially crafted EMF+ metafile. Figure 5 shows the decompiled source code of the affected ScanOperation::AlphaDivide_sRGB() function at the time of the crash.

Figure 5. Decompiled source code of the ScanOperation::AlphaDivide_sRGB() function.

This issue is similar to CVE-2025-30388, the vulnerability we previously discussed. That bug was caused by invalid RECT objects within an EmfPlusSetTSClip record that preceded other records in which the crash occurred. In this new case, the vulnerability stems from a series of EmfPlusRect objects within the affected EmfPlusDrawRects record, detailed in Listing 4.

EmfPlusDrawRects rects = {
    .Type     = 0x400B,
    .Flags    = 0xEF00,
    .Size     = 0x00000048,
    .DataSize = 0x0000003C,
    .Count    = 0x00000003,
    .RectData[Count] = {
        { 0x0000, 0x3400, 0x3434, 0x3434 },
        { 0x3434, 0x3434, 0x3434, 0x3434 },
        { 0x3434, 0x3434, 0x3434, 0x3434 },
        { 0x3434, 0x3434, 0x0000, 0x3000 },
        { 0x3434, 0x3434, 0x3434, 0x3434 },
        { 0x3434, 0x3434, 0x0034, 0x0010 },
        { 0x4000, 0x3434, 0x3434, 0x3434 },
    }
};

Listing 4. Sample EmfPlusDrawRects structure containing malformed EmfPlusRect objects.

The EmfPlusDrawRects record is preceded by an EmfPlusObject record that specifies an EmfPlusPen object used in graphics operations. The EmfPlusPen object defines a graphics brush associated with the pen. The brush is a solid-color brush, characterized by an EmfPlusARGB value. The source of the write operation in the eax register can be controlled by the attacker through this value, as shown by the excerpt of the crash analysis in Listing 5.

0:000> g
(abc.7148): Access violation - code c0000005 (first/second chance not available)
First chance exceptions are reported before any exception handling.
This exception may be expected and handled.
eax=ffff004c ebx=000000ff ecx=fffcf63c edx=086cb9c4 esi=6af57480 edi=0868bd58
eip=6af574b3 esp=004feb34 ebp=004feb48 iopl=0         nv up ei pl zr na pe nc
cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00000246
gdiplus!ScanOperation::AlphaDivide_sRGB+0x33:
6af574b3 890411          mov     dword ptr [ecx+edx],eax ds:002b:0869b000=10000011
0:000> kb
# ChildEBP RetAddr      Args to Child              
00 004feb48 6af573bd     00000035 0868bd0c 004fef98 gdiplus!ScanOperation::AlphaDivide_sRGB+0x33
01 004feb7c 6af5b364     00000064 00000063 00000064 gdiplus!EpScanBitmap::NextBuffer+0xdd
02 004febbc 6af5ab8e     004fef98 004fee40 00000001 gdiplus!OnePixelLineDDAAliased::DrawXMajor+0x54
03 004fecd4 6af5aabf     004fef98 00000000 00000000 gdiplus!DrawSolidLineOnePixelAliased+0x9e
04 004fed18 6af58bb8     004fefac 004fee20 00000005 gdiplus!DrawSolidStrokeOnePixel+0x9f
05 004fef48 6af3fe26     00000000 00000000 6af5aa20 gdiplus!FixedPointPathEnumerate+0x358
06 004feff8 6af405ac     086a7da0 0869cf98 004ff178 gdiplus!DpDriver::SolidStrokePathOnePixel+0x124
07 004ff134 6af43107     086a7da0 0869cf98 004ff178 gdiplus!DpDriver::StrokePath+0x33c
08 004ff15c 6af43096     004ff178 004ff1e8 086b1f8c gdiplus!GpGraphics::DrvStrokePath+0x3b
09 004ff19c 6af429c7     004ff1c4 004ff1e8 086b1f78 gdiplus!GpGraphics::RenderDrawPath+0xbc
0a 004ff39c 6afd3746     086b1f78 07bb0160 00000003 gdiplus!GpGraphics::DrawRects+0x356
0b 004ff3c4 6af40b59     086a4cb0 0000400b 00000400 gdiplus!DrawRectsEPR::Play+0x86
0c 004ff3e8 6af40a67     0000400b 00000400 0000003c gdiplus!GdipPlayMetafileRecordCallback+0x79
0d 004ff41c 6af4094c     00000104 07bb00a8 08006d58 gdiplus!MetafilePlayer::EnumerateEmfPlusRecords+0x97
0e 004ff43c 75eab76f     1c01154a 08d06ff8 07bb0098 gdiplus!EnumEmfWithDownLevel+0x8c
0f 004ff4d0 75ea9d49     6af408c0 086a4cb0 004ff54c gdi32full!bInternalPlayEMF+0x830
10 004ff4e4 75f2b9bf     1c01154a a94624e7 6af408c0 gdi32full!EnumEnhMetaFile+0x39
11 004ff504 6af368f6     1c01154a a94624e7 6af408c0 GDI32!EnumEnhMetaFileStub+0x2f
12 004ff560 6af36551     1c01154a a94624e7 004ff5a4 gdiplus!MetafilePlayer::EnumerateEmfRecords+0xdf
13 004ff608 6af38bdc     086a4cb0 a94624e7 004ff734 gdiplus!GpGraphics::EnumEmfPlusDual+0x351
14 004ff778 6af5454c     004ff7d0 004ff7d0 00000002 gdiplus!GpMetafile::EnumerateForPlayback+0x819
15 004ff898 6af467ac     08683f28 004ff8c8 004ff8d8 gdiplus!GpGraphics::DrawImage+0x5ec
16 004ff904 6afd198e     08683f28 004ff92c 004ff93c gdiplus!GpGraphics::DrawImage+0x61
17 004ff964 6afd2b96     00000064 00000064 08683f28 gdiplus!GpMetafile::GetBitmap+0x1d2
18 004ff978 6afb540b     00000064 00000064 00000000 gdiplus!GpMetafile::GetThumbnail+0x26
19 004ff9a0 009a11a0     08683f28 00000064 00000064 gdiplus!GdipGetImageThumbnail+0x6b
<<<<<<<<<<<<<<unnecessary information removed>>>>>>>>>>>>>>

Listing 5. Stack trace showing an access violation in the ScanOperation::AlphaDivide_sRGB() function.

In a bitmap image, a scan-line is one horizontal row of pixels. Processing an image line by line means handling one scan-line at a time, from left to right and top to bottom. Further analysis showed that the EpScanBitmap::NextBuffer() function never verified that the number of scan-lines it was about to process fit in the destination bitmap, meaning that the function could be tricked into reading or writing past the bottom edge of an image if a call requested more scan-lines than existed.

Assuming that the bitmap allocated for thumbnail generation is 100×100 (0x64 × 0x64) pixels, the rectangle data in the PoC metafile deliberately pushes the scan position past the bottom edge of the bitmap and triggers the out-of-bounds write. Any one of those rectangles forces the rasterizer (which converts vector graphics into a pixel grid) to process scan-lines whose Y coordinate is well beyond the 0-99 range of the bitmap.

Figure 6. Function-level differences showing the code blocks added to the EpScanBitmap::NextBuffer() function.

Microsoft fixed this vulnerability within the EpScanBitmap::NextBuffer() function in version 10.0.26100.4946 of GdiPlus.dll, as shown in Figure 6, by adding a check to detect when the requested number of scan-lines exceeds the height of the bitmap. The function now automatically trims the requested scan-lines to fit within the remaining rows, preventing any out-of-bounds access, as shown in Figure 7.

Figure 7. Decompiled source code of the patched GEpScanBitmap::NextBuffer() function.

This vulnerability was addressed with KB5063878 in the August 2025 Patch Tuesday as a critical severity remote code execution vulnerability tracked as CVE-2025-53766. Notably, this vulnerability requires no privileges or user interaction and can be exploited remotely over a network, making it a high-risk threat to web services that parse specially crafted metafiles.

Unfinished Business – CVE-2025-48984

We identified a fifth crash while processing an EMR_STARTDOC record, which immediately appeared to be related to the CVE-2022-35837 vulnerability. An access violation exception happened in the StringLengthWorkerW() function within version 10.0.26100.3624 of gdi32full.dll while attempting to read memory at the end of a 288/0x120 bytes heap block. Listing 6 contains the relevant excerpt of the crash analysis.

0:000> g
(48b0.7b68): Access violation - code c0000005 (first chance)
First chance exceptions are reported before any exception handling.
This exception may be expected and handled.
eax=00dbf028 ebx=08195f7c ecx=08196000 edx=7fffffbc esi=7ffffffe edi=00000000
eip=7792d586 esp=00dbf014 ebp=00dbf01c iopl=0         nv up ei pl nz na po nc
cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00010202
gdi32full!StringLengthWorkerW+0xf:
7792d586 663939          cmp     word ptr [ecx],di        ds:002b:08196000=????
0:000> kb
 # ChildEBP RetAddr      Args to Child              
00 00d3f37c 7796f55c     00d3f388 00000000 00d3f3cc gdi32full!StringLengthWorkerW+0xf
01 00d3f38c 77974155     00d3f3b0 0885bf4c 779740b0 gdi32full!StringCbLengthW+0x1a
02 00d3f3cc 77966ffb     9e011903 0885dff8 00000001 gdi32full!MRSTARTDOC::bPlay+0xa5
03 00d3f420 6d971f4d     9e011903 0885dff8 0885bf4c gdi32full!PlayEnhMetaFileRecord+0x5b
04 00d3f438 6d971e21     08ab5720 6d971c40 0000006b gdiplus!EmfEnumState::PlayRecord+0x2d
05 00d3f450 6d980b8c     0000006b 00000024 0885bf54 gdiplus!EmfEnumState::ProcessRecord+0x1e1
06 00d3f470 6d9b939d     0000006b 00000000 00000024 gdiplus!GdipPlayMetafileRecordCallback+0xec
07 00d3f49c 7796824f     9e011903 0885dff8 0885bf4c gdiplus!EnumEmfDownLevel+0x7d
08 00d3f530 77966829     6d9b9320 08ab2cb0 00d3f5ac gdi32full!bInternalPlayEMF+0x830
09 00d3f544 7632ad7f     9e011903 65461856 6d9b9320 gdi32full!EnumEnhMetaFile+0x39
0a 00d3f564 6d9768c6     9e011903 65461856 6d9b9320 GDI32!EnumEnhMetaFileStub+0x2f
0b 00d3f5c0 6d9741ac     9e011903 65461856 00d3f664 gdiplus!MetafilePlayer::EnumerateEmfRecords+0xdf
0c 00d3f678 6d9789eb     08ab2cb0 65461856 00d3f7b4 gdiplus!GpGraphics::EnumEmf+0x413
0d 00d3f7f8 6d99452c     00d3f850 00d3f850 00000002 gdiplus!GpMetafile::EnumerateForPlayback+0x658
0e 00d3f918 6d98676c     08a93f28 00d3f948 00d3f958 gdiplus!GpGraphics::DrawImage+0x5ec
0f 00d3f984 6da108fe     08a93f28 00d3f9ac 00d3f9bc gdiplus!GpGraphics::DrawImage+0x61
10 00d3f9e4 6da11b06     00000064 00000064 08a93f28 gdiplus!GpMetafile::GetBitmap+0x1d2
11 00d3f9f8 6d9f439b     00000064 00000064 00000000 gdiplus!GpMetafile::GetThumbnail+0x26
12 00d3fa20 00db11aa     08a93f28 00000064 00000064 gdiplus!GdipGetImageThumbnail+0x6b
<<<<<<<<<<<<<<unnecessary information removed>>>>>>>>>>>>>>

Listing 6. Stack trace showing an access violation in the StringLengthWorkerW() function.

The stack trace suggests that the issue may lie with the StringLengthWorkerW() function, which performs a length check on user-controlled data and assumes the input is a null-terminated string. However, if the provided string is not null-terminated, the function may read beyond the allocated buffer, leading to potential information disclosure.

The decompiled source code of the MRSTARTDOC::bPlay() function shown in Figure 8 demonstrates that CVE-2022-35837 was addressed by assigning values to the lpszDocName and lpszOutput fields through a calculated offset stored in the v5 variable, if their respective fields are non-null.

Figure 8. Decompiled source code of the MRSTARTDOC::bPlay() function.

Listing 7 shows the affected EMR_STARTDOC metafile record. The DOCINFO structure contains the input and output file names and other information used by the StartDoc() function, which starts a print job.

EMR_STARTDOC startDoc = {
    .Type             = 0x0000006b,
    .Size             = 0x0000002c,
    .docInfo = {
        .cbSize       = 0x00000020,
        .lpszDocName  = 0x2b464d45,  // -> 0x1002
        .lpszOutput   = 0x00004001,  // -> 0x6b14
        .lpszDatatype = 0x0000001c,
        .fwType       = 0x00000010
    }
};

Listing 7. Sample EMR_STARTDOC record with a DOCINFO structure.

The raw EMR_STARTDOC record shown in Listing 8 demonstrates that after patching the originally reported arbitrary information disclosure, the lpszDocName field points to 0x1002, located immediately after the record at offset 1Ch. Meanwhile, the lpszOutput field points to 0x6b14, positioned at offset 1Ch + 14h = 30h:

0000h  6B 00 00 00 2C 00 00 00 20 00 00 00 45 4D 46 2B  k...,... ...EMF+ 
0010h  01 40 00 00 1C 00 00 00 10 00 00 00 02 10 C0 DB  .@............ÀÛ 
0020h  01 00 00 05 05 7F FF 00 60 00 00 00 46 00 00 00  .....ÿ.`...F... 
0030h  14 6B 6B 6B 6B 6B 6B 6B 6B 6B 6B 6B 6B 6B 5C 6B  .kkkkkkkkkkkkk\k 
0040h  6B 6B 6B 6B 6B 6B 6B 6B 6B 10 6B 6B 6B 6B 6B 6B  kkkkkkkkk.kkkkkk 
0050h  6B 6B 6B 6B                                      kkkk

Listing 8. Raw EMR_STARTDOC record showing the calculated offsets.

The out-of-bounds read occurs because the MRSTARTDOC::bPlay() function validates string offsets inside the record. A specially crafted metafile may pad the first string in the lpszDocName field so that it nearly reaches the end of the record. After copying that string, the code advances its internal cursor beyond that point, but the next validation for the lpszOutput field still treats the supplied offset as if it were relative to the original base of the record.

This discrepancy allows an attacker to provide a value that passes the MR::bValidOff() function while actually pointing outside the heap block. Because no null terminator is found, the StringLengthWorkerW() function continues reading into adjacent memory, exposing its contents. The crash sample intensifies the issue by setting the EMF_HEADER.nBytes field to just 0x120 (288) bytes, causing the allocated buffer to be smaller than the embedded data and guaranteeing an over-read.

Microsoft fixed this vulnerability within the MRSTARTDOC::bPlay() handler function in version 10.0.26100.4652 of gdi32full.dll by correcting the offset arithmetic. The patched function now converts the pointer back to an offset relative to the start of the record before revalidating it and applies the same logic to the lpszOutput field. Therefore, the check matches the data that will be dereferenced, as shown in Figure 9.

Figure 9. Decompiled source code of the patched MRSTARTDOC::bPlay() function.

This bug was addressed with KB5062553 in the July 2025 Patch Tuesday as an important severity information disclosure vulnerability tracked as CVE-2025-47984. MSRC classified it as CWE-693: Protection Mechanism Failure, which in this case really means that the security fix to address CVE-2022-35837 was incomplete.

Conclusion

We discovered vulnerabilities in Windows GDI that could have serious implications for system security. Our extensive investigation into EMF+ files shows that staying ahead of potential threats requires continuous diligence and adaptation. By sharing these findings, we hope to raise awareness and provide valuable insights and recommendations to enhance security for all Windows users.

Security vulnerabilities can persist undetected for years, often resurfacing due to incomplete fixes. A particular information disclosure vulnerability, despite being formally addressed with a security patch, remained active for years due to the original issue receiving only a partial fix. This example underscores a basic conundrum for researchers: introducing a vulnerability is often easy, fixing it can be difficult, and verifying that a fix is both thorough and effective is even more challenging.

These issues highlight why comprehensive and continuous security testing, using verification techniques that must be constantly updated and improved, is crucial. This effort can be greatly enhanced by close collaboration between vendors and security researchers, including sharing planned fixes with the researchers who initially reported the issue. Such a collaborative approach adds an extra layer of review, helping to catch potential gaps early and strengthens the overall security of the software ecosystem.

POPULAR POSTS

BLOGS AND PUBLICATIONS

  • Check Point Research Publications
  • Global Cyber Attack Reports
  • Threat Research
February 17, 2020

“The Turkish Rat” Evolved Adwind in a Massive Ongoing Phishing Campaign

  • Check Point Research Publications
August 11, 2017

“The Next WannaCry” Vulnerability is Here

  • Check Point Research Publications
January 11, 2018

‘RubyMiner’ Cryptominer Affects 30% of WW Networks