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/dataUnicodeStringCopyCount (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.*