Yesterday we noticed a blog post and securityfocus article about a potential new vulnerability in Microsoft GDI+ when parsing a specially-crafted EMF file. You might have heard about it referred to as ‘GpFont.SetData()’. We wanted to address some speculation about this EMF parsing bug.
First, our initial investigation shows that it is not exploitable for code execution. We are still investigating all the potential ways to hit this code but in all the common cases so far, our /GS mitigation is an effective defense-in-depth measure. The EMF parsing bug ends up writing 0x0000, a single Unicode 0 character, over the lower two bytes of the /GS security cookie. Only those two bytes are overwritten and the application is terminated due to the /GS failure.
Here’s where the issue occurs:
0:000> k
ChildEBP RetAddr
0007e37c 4ec9e783 gdiplus!GpFont::SetData+0x6a
0007e3a0 4ec9e70f gdiplus!MetafilePlayer::AddObject+0x8f
0007e3b4 4ec9d1b5 gdiplus!ObjectEPR::Play+0x1a
0007e3d0 4ec9ce34 gdiplus!GdipPlayMetafileRecordCallback+0x35
0007e3fc 4ec9cd4e gdiplus!MetafilePlayer::EnumerateEmfPlusRecords+0x66
0007e414 77f2072f gdiplus!EnumEmfWithDownLevel+0x52
0007e490 4ec9e625 gdi32!bInternalPlayEMF+0x707
0007edd4 4eca0c90 gdiplus!MetafilePlayer::EnumerateEmfRecords+0xd7
0007ee70 4ec9e67f gdiplus!GpGraphics::EnumEmfPlusDual+0x27d
0007ef90 4ed03350 gdiplus!GpMetafile::EnumerateForPlayback+0x686
0007efc0 4ec9bb58 gdiplus!GpMetafile::Play+0x26
The gdiplus!GpFont::SetData code shows an obvious off-by-one bug. This was subsequently fixed before Vista shipped. Here’s the Windows XP code:
#define FamilyNameMax 32
…
WCHAR familyName[FamilyNameMax];
…
length = fontData->Length; // this comes from the EMF file
…
if (length > FamilyNameMax)
{
length = FamilyNameMax;
}
…
// read in the familyName/data
UnicodeStringCopyCount (familyName, (WCHAR *)dataBuffer, length);
familyName[length]=0;
Here’s what it looks like from the assembly:
gdiplus!GpFont::SetData:
4ecff9e9 8bff mov edi,edi
4ecff9eb 55 push ebp
4ecff9ec 8bec mov ebp,esp
4ecff9ee 83ec44 sub esp,44h
4ecff9f1 a10000dd4e mov eax,dword ptr [gdiplus!__security_cookie (4edd0000)] ds:0023:4edd0000=00006ea3
4ecff9f6 53 push ebx
4ecff9f7 8945fc mov dword ptr [ebp-4],eax // Security cookie saved in ebp-4
4ecff9fa 8b4508 mov eax,dword ptr [ebp+8]
You can see from the top of the function that the security cookie is 00006ea3. Now let’s look lower.
4ecffa29 8b4804 mov ecx,dword ptr [eax+4]
4ecffa2c 894f10 mov dword ptr [edi+10h],ecx
4ecffa2f 8b4808 mov ecx,dword ptr [eax+8]
4ecffa32 894f18 mov dword ptr [edi+18h],ecx
4ecffa35 8b480c mov ecx,dword ptr [eax+0Ch]
4ecffa38 894f14 mov dword ptr [edi+14h],ecx
4ecffa3b 8b7014 mov esi,dword ptr [eax+14h]
4ecffa3e 8d4c3618 lea ecx,[esi+esi+18h]
4ecffa42 83c018 add eax,18h
4ecffa45 394d0c cmp dword ptr [ebp+0Ch],ecx
4ecffa48 0f8283000000 jb gdiplus!GpFont::SetData+0xe8 (4ecffad1)
4ecffa4e 83fe20 cmp esi,20h
4ecffa51 7603 jbe gdiplus!GpFont::SetData+0x6d (4ecffa56)
4ecffa53 6a20 push 20h
4ecffa55 5e pop esi
4ecffa56 56 push esi
4ecffa57 50 push eax
4ecffa58 8d45bc lea eax,[ebp-44h] //0x20 WCHAR buffer
4ecffa5b 50 push eax
4ecffa5c e8d7750600 call gdiplus!GpRuntime::UnicodeStringCopyCount (4ed67038)
4ecffa61 a15009dd4e mov eax,dword ptr [gdiplus!Globals::FontCollection (4edd0950)]
4ecffa66 66895c75bc mov word ptr [ebp+esi*2-44h],bx
4ecffa6b 8b7008 mov esi,dword ptr [eax+8]
4ecffa6e 395e08 cmp dword ptr [esi+8],ebx
If we break in just before that string copy, we see the following:
eax=00913658 ebx=00000000 ecx=00000020 edx=05aa010c esi=00000020 edi=0091c798
eip=4ecffa66 esp=0007e32c ebp=0007e37c iopl=0 nv up ei pl zr na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000246
gdiplus!GpFont::SetData+0x7d:
4ecffa66 66895c75bc mov word ptr [ebp+esi*2-44h],bx ss:0023:0007e378=6ea3
Remember from the assembly above that the security cookie was 00006ea3. Here’s how memory looks before the mov instruction:
0:000> dd ebp-0x10
0007e36c 44332211 001d0b0b 44332211 00006ea3
0007e37c 0007e3a0 4ec9e783 05aa00b4 00000024
0007e38c 0091e008 0091e008 00000030 0091e001
0007e39c 0091e008 0007e3b4 4ec9e70f 00000000
0007e3ac 05aa00b4 00000024 0007e3d0 4ec9d1b5
0007e3bc 0091e008 00004008 00000600 00000024
0007e3cc 05aa00a8 0007e3fc 4ec9ce34 00004008
0007e3dc 00000600 00000024 05aa00b4 0091e008
You can see the cookie on the stack protecting the return address. And then after the copy it is overwritten:
0:000> dd ebp-0x10
0007e36c 44332211 001d0b0b 44332211 00000000
0007e37c 0007e3a0 4ec9e783 05aa00b4 00000024
0007e38c 0091e008 0091e008 00000030 0091e001
0007e39c 0091e008 0007e3b4 4ec9e70f 00000000
0007e3ac 05aa00b4 00000024 0007e3d0 4ec9d1b5
0007e3bc 0091e008 00004008 00000600 00000024
0007e3cc 05aa00a8 0007e3fc 4ec9ce34 00004008
0007e3dc 00000600 00000024 05aa00b4 0091e008
As always, we encourage responsible disclosure of potential vulnerabilities. Best way to get ahold of us is secure@microsoft.com. Thanks.
- Jonathan Ness, MSRC Engineering
*Postings are provided “AS IS” with no warranties, and confers no rights.*