Yet Another Windows GDI Story

You’ve reached an archived Flexera blog post that may be out of date. Please visit the blog homepage for the most current posts.


The Microsoft Security Bulletin MS15-035(1) resolved a vulnerability in Windows GDI, which can be exploited to execute arbitrary code via EMF files. This vulnerability was discovered by Secunia Research and has been assigned Secunia Advisory SA60006 (2)(3).

The vulnerability is rated Highly Critical by Secunia Research.

Windows Graphics Device Interface (GDI)

The Graphics Device Interface (GDI) is a Microsoft Windows application programming interface and core operating system component responsible for representing graphical objects and transmitting them to output devices such as monitors and printers.(4) The processing of EMF files utilizes Windows GDI.

EMF File Format

An EMF metafile is a series of variable-length records, called EMF records, which contain graphics drawing commands, object definitions, and properties. The metafile begins with a header record, which includes the metafile version, its size, the resolution of the device on which the picture was created, and the dimensions of the picture. An EMF metafile is “played back” when its records are converted to a format understood by a specific graphics device (5). The EMR_SETDIBITSTODEVICE record (record type 80) is one of the records available in EMF files. This record specifies a block transfer of pixels from specified scan lines of a source bitmap to a destination rectangle,(6) and contains various fields for that purpose.

Technical Details

The following analysis is based on Windows 7 Professional using gdi32.dll version 6.1.7601.18577 and GdiPlus.dll version 6.1.7601.18455.

When processing EMF records, the processing flow reaches the “EmfEnumState::ProcessRecord()” function within gdiplus.dll to process each records.

.text:25D40BCE lea eax, [edi-1] ; switch 122 cases ; edi= record type
.text:25D40BD1 cmp eax, 79h
.text:25D40BD4 ja short loc_25D40C20 ; jumptable 25D40BDD default case
.text:25D40BD6 movzx eax, ds:byte_25D40DD7[eax]
.text:25D40BDD jmp ds:off_25D40D5B[eax*4] ; switch jump

For an EMR_SETDIBITSTODEVICE record a jump to loc_25D40C9D is made, where a call to the “EmfEnumState::SetDIBitsToDevice()” function is following.

.text:25D40C9D mov ecx, esi ; jumptable 25D40BDD case 80
.text:25D40C9F call EmfEnumState::SetDIBitsToDevice(void)
.text:25D40CA4 jmp short loc_25D40C7B

After additional processing the “EmfEnumState::PlayRecord()” function is reached, where the “PlayEnhMetaFileRecord()” function within gdi32.dll is called. A pointer to the record is passed as 3rd parameter in this case.

.text:25D3FA0D push dword ptr [esi+14h] ; cht
.text:25D3FA10 push eax ; pointer to the record
.text:25D3FA11 push dword ptr [esi+50h] ; pht
.text:25D3FA14 push dword ptr [esi+8] ; hdc
.text:25D3FA17 call PlayEnhMetaFileRecord(x,x,x,x)

The “PlayEnhMetaFileRecord()” function is responsible for playing an enhanced-metafile record by executing the graphics device interface (GDI) functions identified by the record.(7) Depending on the record type, a different function will be called with respect to the record by utilizing the record type as an index into a call table. An EMR_SETDIBITSTODEVICE record (record type 80) will lead to a call to the “MRSETDIBITSTODEVICE::bPlay()” function based on this.

.text:77B75DEA push [ebp+cht]
.text:77B75DED mov ecx, esi ; passing pointer to the record as this pointer.
.text:77B75DEF push [ebp+pht]
.text:77B75DF2 push [ebp+hdc]
.text:77B75DF5 call dword ptr ds:(loc_77B75E0B+1)[eax*4] ; eax= 0x50 (EMR_SETDIBITSTODEVICE)

The “MRSETDIBITSTODEVICE::bPlay()” function starts by checking the provided record and returns an error if checks are not passed.

.text:77BA3516 ; =============== S U B R O U T I N E =======================================
.text:77BA3516 ; Attributes: bp-based frame
.text:77BA3516 ; int __thiscall MRSETDIBITSTODEVICE::bPlay(MRSETDIBITSTODEVICE *this, HDC hdc, struct tagHANDLETABLE *, unsigned int)
.text:77BA3516 ; DATA XREF: .text:77B75F4Co
.text:77BA3516 pt = tagPOINT ptr -0Ch
.text:77BA3516 var_4 = dword ptr -4
.text:77BA3516 hdc = dword ptr 8
.text:77BA3516 arg_4 = dword ptr 0Ch
.text:77BA3516 mov edi, edi
.text:77BA3518 push ebp
.text:77BA3519 mov ebp, esp
.text:77BA351B sub esp, 0Ch
.text:77BA351E mov eax, [ebp+arg_4]
.text:77BA3521 push ebx
.text:77BA3522 push esi
.text:77BA3523 push edi
.text:77BA3524 push 460000h
.text:77BA3529 push dword ptr [eax]
.text:77BA352B mov esi, ecx ; pointer to the record
.text:77BA352D xor edi, edi
.text:77BA3544 push [ebp+arg_4] ; struct tagHANDLETABLE *
.text:77BA3547 mov ecx, esi ; this
.text:77BA3549 call MRSETDIBITSTODEVICE::bCheckRecord(tagHANDLETABLE *)
.text:77BA354E test eax, eax
.text:77BA3550 jz short return_0
.text:77BA3552 lea eax, [esi+EMRSETDIBITSTODEVICE.rclBounds]
.text:77BA3555 push eax ; struct ERECTL *
.text:77BA3556 mov ecx, ebx ; this
.text:77BA3558 call MF::bClipped(ERECTL &)
.text:77BA355D test eax, eax
.text:77BA355F jz short loc_77BA3569
.text:77BA3569 mov eax, [esi+EMRSETDIBITSTODEVICE.xDest]
.text:77BA356C mov [ebp+pt.x], eax
.text:77BA356F mov eax, [esi+EMRSETDIBITSTODEVICE.yDest]
.text:77BA3572 mov [ebp+pt.y], eax
.text:77BA3575 push 1 ; c
.text:77BA3577 lea eax, [ebp+pt]
.text:77BA357A push eax ; lppt
.text:77BA357B push dword ptr [ebx+2A4h] ; hdc
.text:77BA3581 call LPtoDP(x,x,x)
.text:77BA3586 test eax, eax
.text:77BA3588 jz short return_0
.text:77BA359D push [esi+EMRSETDIBITSTODEVICE.cbBmiSrc] ; unsigned __int32
.text:77BA35A0 mov ecx, esi ; this
.text:77BA35A2 push [esi+EMRSETDIBITSTODEVICE.offBmiSrc] ; unsigned __int32
.text:77BA35A5 push [ebp+arg_4] ; struct tagHANDLETABLE *
.text:77BA35A8 call MR::bValidOffExt(tagHANDLETABLE *,ulong,ulong)
.text:77BA35AD test eax, eax
.text:77BA35AF jz short return_0

If all checks are passed, then a new buffer is allocated based on the cbBmiSrc field of the record. This field specifies size of the source BITMAPINFO structure. Note that this field is user controlled and it is possible to create a crafted record with cbBmiSrc equal to e.g. 4.

.text:77BA35B1 push [esi+EMRSETDIBITSTODEVICE.cbBmiSrc] ; uBytes , controlled
.text:77BA35B4 push 0 ; uFlags
.text:77BA35B6 call ds:__imp__LocalAlloc@8 ; LocalAlloc(x,x)
.text:77BA35BC mov ebx, eax
.text:77BA35BE test ebx, ebx
.text:77BA35C0 jz loc_77BA364B

Further on, data is written into this allocated buffer and, by specifying a small value for the controlled cbBmiSrc field, it will be possible to allocate a small buffer, which in turn leads to corruption of memory.

.text:77BA35C6 push [esi+EMRSETDIBITSTODEVICE.cbBmiSrc] ; Size
.text:77BA35C9 mov eax, [esi+EMRSETDIBITSTODEVICE.offBmiSrc]
.text:77BA35CC add eax, esi
.text:77BA35CE push eax ; Src
.text:77BA35CF push ebx ; Dst
.text:77BA35D0 call _memcpy
.text:77BA35D5 mov eax, [esi+EMRSETDIBITSTODEVICE.cScans]
.text:77BA35D8 add esp, 0Ch
.text:77BA35DB cmp [ebx+8], edi
.text:77BA35DE jg short loc_77BA35E2
.text:77BA35E0 neg eax
.text:77BA35E2 loc_77BA35E2: ; CODE XREF: MRSETDIBITSTODEVICE::bPlay(void *,tagHANDLETABLE *,uint)+C8j
.text:77BA35E2 mov [ebx+8], eax ; *** corruption here ***
.text:77BA35E5 mov eax, [esi+EMRSETDIBITSTODEVICE.cbBitsSrc]
.text:77BA35E8 mov [ebx+14h], eax ; *** corruption here ***
.text:77BA35EB cmp [esi+EMRSETDIBITSTODEVICE.cbBitsSrc], edi
.text:77BA35EE jz short loc_77BA3604

Exploitation Vectors

As the processing of any encountered EMF file utilizes Windows GDI, there are several possible exploitation vectors available. For example, simply sending a malicious EMF file as an email attachment can be used to trigger this vulnerability. Additionally, it is also possible to embed a malicious EMF file in a webpage or in a file format suitable for an office suite application to exploit this issue.



Leave a Reply

Your email address will not be published. Required fields are marked *