CVE-2017-0190 is a recently patched vulnerability related to Windows metafiles (WMFs), a portable image format mainly used by 16-bit Windows applications. Recently we have seen an increase in the number of vulnerabilities related to WMFs and EMFs (enhanced metafiles) in the GDI32 library.
Most often, these vulnerabilities lead to sensitive information disclosure from the process heap. They can also open the door to arbitrary code execution if the attacker uses them in combination with other vulnerabilities. Because WMFs are used in popular applications such as Microsoft Office, Paint, and Internet Explorer, the vulnerability has a huge attack surface.
CVE-2017-0190 describes a vulnerability in a maliciously crafted WMF that contains a record calling the function SetDIBitsToDevice() with malformed parameters from the GDI32 library, and can lead to information disclosure.
A WMF stores a list of function calls that are issued to the Windows Graphics Device Interface (GDI) layer to display an image on screen.
Figure 1: Format of Window metafile.
The format consists of a header and an array of metafile records, which store the function ID and the parameters required for it. The GDI32 PlayMetaFileRecord function parses each record and invokes the function corresponding to the function ID in the metafile record.
The minimum size for a metafile is 18 bytes. A file of this size contains the header and no records. The format of the file header:
Figure 2: Format of a metafile header.
Each record is of variable size, depending on the function call and number of parameters passed. The format of each record:
Figure 3: Format of a metafile record.
The final record in a WMF should be an end-of-file record. This comprises three words, and its function ID is zero.
A malformed WMF is parsed by PlayMetafileRecord(), which calls the SetDIBitsToDevice() function based on the function ID field of the record. SetDIBitsToDevice() belongs to the GDI32 library and calling it with improper parameters can cause an out-of-bounds-read vulnerability.
SetDIBitsToDevice()paints a device independent bitmap (DIB) onto a device. This involves copying color data from a DIB to the memory device context (MDC). An out-of-bounds read happens when memcpy is used to copy DIB data to the MDC of the malformed metafile.
The record that calls SetDIBitsToDevice():
Figure 4: A malformed WMF record.
When the malformed WMF executes, the parameters are converted to arguments for SetDIBitsToDevice():
Figure 5: Parameters for the function SetDIBitsToDevice().
A DIB contains color and dimension information. A DIB structure consists of a bitmap header followed by the color data. Windows supports two types of bitmap headers, bitmapinfoheader and bitmapcoreheader; the DIB structure is called bitmapinfo or bitmapcoreinfo.
In Figure 5, lpvBits points to the color data and lpbmi points to the bitmap header. The structure of the bitmapinfoheader and bitmapcoreheader:
Figure 6: Bitmap header structures.
The minimum value for the bcSize field of bitmapcoreheader is 0xc and that of bitmapinfoheader is 0x28 (header size).
For CVE-2017-0190, the value of the bcSize field in the DIB record for a malformed WMF is 0xc, as shown in Figure 4, which confirms that the structure is a bitmapcoreheader.
Figure 7: BitmapCoreInfo from the record.
The function PlayMetafileRecord() calculates the size of the DIB data in the metafile record. This is 0xC for the malformed record. The size calculated by PlayMetafileRecord() is passed as a parameter to the CheckAndGetBitmapBits() function, which compares it with 0x28. If the value is less than 0x28, CheckAndGetBitmapBits() parses it as the bitmapcoreinfo structure. However, there is no color data following the header. So the source for copying the data in SetDIBitsToDevice() contains null.
The destination is an MDC structure. The PvNewRecord() function creates the MDC buffer. The size of the buffer is calculated from the length field of the cScanLines(0xffff) field of the record, shown in Figure 5. It then calls memcpy to copy color data from the source to the destination.
The length parameter for memcpy is calculated from the cScanLines parameter passed to SetDIBitsToDevice, which is 0xffff. This results in an out-of-bounds read.
Figure 8: Crash in memcpy.
Microsoft fixed CVE-2017-0190 in the May patch.
Figure 9: Comparing the code with patch diff.
As we explained, in the unpatched version CheckAndGetBitmapBits()compares the size of the DIB calculated by PlayMetafileRecord() with 0x28. If the value is less than 0x28, it parses the DIB as bitmapcoreinfo structure.
But in the patched version, if the value is less than 0x28, CheckAndGetBitmapBits() clears the return value, that is, sets it to zero and exits. Thus, PlayMetafileRecord() moves to the next record, leaving behind the malformed record.
Our analysis of the patch shows that Windows no longer uses the bitmapcoreheader structure for a DIB in a metafile record, thereby preventing many vulnerabilities associated with it.
A WMF can be used with many applications that use the gdi32 library. This increases the risk because the attack surface is large. All Windows users should keep their systems patched with the latest updates.
McAfee blocks this attack with signature ID: 0x45206000.