Investigating Windows Graphics Vulnerabilities: A Reverse Engineering and Fuzzing Story
It is not surprising that vulnerabilities targeting Windows applications and components are on the most-wanted list. A study conducted by Recorded Future rates eight of the top 10 most exploited vulnerabilities in 2018 were Microsoft Office bugs. How is that possible? Well, for example, if we just inspect the modules loaded by a Microsoft Excel instance at startup, we can observe that there are 91 DLLs, which represents an impressive attack surface. In comparison, notepad.exe or calc.exe need about 30-40 DLLs to run.
Process Explorer: Inspecting Microsoft Excel Modules
Specifically, each vulnerability found in one of these modules could become a vulnerability of Microsoft Excel itself. One of the potential attack vectors could be serving a malicious document to a user and manipulating him into opening it. It is enough to find just one vulnerable module to compromise the whole application and ultimately infect the system. That is the greatest advantage of the attacker, to have all the time necessary to carefully study the target to find the right way in. This is precisely why a system is as strong as its weakest link.
Here at the Application and Threat Intelligence (ATI) Research Center, one of our objectives is to identify vulnerabilities in applications, report them, and aid developers in fixing them before they are exploited. This blogpost is meant to exemplify this process, by explaining how to root-cause a Microsoft-related vulnerability through patch-diffing and reverse-engineering, and showcase the steps required to obtain an application crash related to this specific vulnerability. This is helpful for a security researcher, since an application crash is an essential building block for a successful exploit.
To this end, we will investigate the recent CVE-2019-0618, a remote code execution vulnerability of the Windows GDI+ component. A vulnerability doesn’t require a fancy, frightening name such as ETERNALBLUE or ETERNALROMANCE for it to wreak havoc, that is just a way of gaining notoriety. It doesn’t even require a great CVSS score either. In certain cases, you can chain multiple low-score vulnerabilities together to completely compromise a system. It has happened before. Such considerations render CVE-2019-0618 a good example of a potential vulnerability to be exploited at large. An exploit for such a vulnerability could be used, for example, to execute malware on targets of interest. For a detailed overview of the techniques employed by the attackers for malware evasion, refer to another of our blog post here.
GDI+ is a C/C++ class-based API that enables applications to use graphics and formatted text on video displays and printers. Applications based on the Microsoft Win32 and Win64 API do not access graphics hardware directly. Instead, GDI+ interacts with device drivers on behalf of applications. GDI+ can be used in all Windows-based applications. This deems GDI+ API the main tool for rendering Windows forms and managing their content.
At the time of writing, ZDI already has 9 published vulnerabilities against GDI+ , one of them being the case-study vulnerability. From the public ZDI report, we are dealing with a heap-based buffer overflow vulnerability, which could lead to remote code execution. Furthermore, the report states that the vulnerability has a CVSSv2 score of 9.2 and it lies within the DoRotatedStretchBlt method, a useful mention for the analysis to follow. No other pieces of information relevant to our investigation, nor a proof of concept (PoC) for this specific vulnerability have been publicly disclosed.
For starters, we want to understand how the vulnerability was patched. This should allow us to deduce what the exploit should look like. To do that, we must narrow down the differences added by the patch by comparing a DLL version close to the patched one. The methodology employed for a patch-diffing operation is detailed in another one of our blog posts here. In short, we have compared a patched Windows 7 x86 GdiPlus DLL to an unpatched one.
Gdiplus.dll: unpatched (left) vs patched (right)
One can infer all kinds of useful information from a patch diffing operation. For example, looking at the number of patched methods can tell us about the importance of that specific update. Watching and comparing successive patches for one single module can give us some intel into the development process, such as the kind of issues/ classes of vulnerabilities that arise within the specific component. This approach can help the security researcher identify targets for fuzzing or interesting functionalities more prone to bugs.
Using the BinDiff IDA plugin against the patched and unpatched library, we get these results:
BinDiff output: GdiPlus.dll comparison
The comparison confirms the presence of some differences within the DoRotatedStretchBlt method, paired with a good degree of confidence. Let’s look closer at the block differences.
BinDiff output: gdiplus!DoRotatedStretchBlt method, patched versus unpatched
The BinDiff plugin color mapping is:
- The green nodes indicate basic blocks that have identical instructions in both executables
- The red nodes indicate basic blocks to which the comparison algorithms were unable to find equivalents
- The yellow nodes indicate nodes for which the algorithms could find equivalents, but which had some instructions changed between versions
In our case, we can observe that there are some red blocks in the patched DLL, so let’s zoom in using IDA Hex-Rays Disassembler to see what the actual differences are.
IDA output: GdiPlus!DoRotatedStretchBlt method, patched (left) vs unpatched (right)
Inspecting the patch, the conclusion is that we are dealing with a classic case of using memcpy without proper boundary checks. The patch seems to treat different values of the Size parameter, to avoid any overflow. Oddly, it seems that not all the cases were handled accordingly. That happens because, as it is the case with Windows patches, the fix won’t be present at the exact place where vulnerability lies. A further look reveals another part of the fix, present in the handler for the StretchBlt record:
IDA output: GdiPlus!_bHandleStretchBlt method, patched (left) vs unpatched (right)
We notice that, aside from the GdiPlus!pfnIsValidEnhMetaRecordOffExt method that is also present in the unpatched version of the library, the patch contains another validation method, namely GdiPlus!bValidateAndExpandBuffers. Its job is to sanitize the Src buffer (pjBits) and Ubytes (v21) parameters. Then, DoStretchBlt will call our vulnerable function if certain conditions are met.
IDA output: GdiPlus!DoStretchBlt method
The function of interest is called if an ANDing operation against hdc_flag evaluates to true. The variable hdc refers to a Windows Device Context. A device context is a structure that defines a set of graphic objects and their associated attributes, as well as the graphic modes that affect output. In our case, the graphic objects could include a region for clipping, painting and drawing operations.
In conclusion, to exploit this vulnerability, we must gain control of the Src and Ubytes parameters of the DoRotatedStretchBlt method. But first, we must find a way to reach it. The best way to understand how we can reach the function is to use IDA’s call graph feature.
Call graph to gdiplus!DoRotatedStretchBlt
According to the call graph, we can make a DoRotatedStretchBlt operation by calling different handlers (e.g., bHandlePlgBlt or bHandleSetDIBitsToDev). These seem to be handlers for different EMF records. The question that arises is then how are different EMF records processed?
Understanding EMF Format
Enhanced metafile format (EMF) is a file format that is used to store portable representations of graphical images. EMF metafiles contain sequential records that are parsed and processed to render the stored image on any output device. These record types must somehow be accounted for in the library, such that, when an EMF file is processed, it will serve the right handler for it. A quick search within the binary gets us to the _pdofnDrawingOrders method:
GdiPlus!pdofnDrawingOrders containing references to each record handler
As we can observe, all the supported records are enumerated here, including our bHandleStretchBlt record handler. Let’s find a way to trigger a DrawingOrder operation.
IDA: GdiPlus!bParseWin32Metafile method
The GdiPlus!bParseWin32Metafile method will parse a WMF file header, and if it contains valid EMF records, it will parse each of them accordingly by calling the correct handler from the _pdofnDrawingOrders. The parsing method is in turn called by GdipConvertEmfToWmFBits. There are no further cross-references to this method, which indicates that there must be some external API for it. Going through the Win GDI+ documentation, it seems that GdipConvertEmfToWmFBits is deprecated, but we have a wrapper for it within the Metafile class, namely Metafile::EmfToWmfBits. It’s almost time to bring out the champagne - we found a way in.
Let’s stop for a moment and evaluate what have we learned so far:
- The vulnerable method gdiplus!DoRotatedStretchBlt is called by gdiplus!DoStretchBlt which handles the EMF_STRETCHBLT record type.
- The vulnerable method will be called if a certain flag is set for the device handle (value 4).
- We can eventually call the vulnerable method by triggering a Metafile::EmfToWmfBits operation, having as one of the parameters an EMF file containing an EMF_STRETCHBLT record.
At this point we should have enough information to pause the reversing process and attempt to come up with an EMF file that would crash within the desired vulnerable method through fuzzing, while hopefully also finding some other interesting corner-cases as well.
1. Choosing the fuzzer
The current state-of-the-art fuzzing frameworks for Windows binaries include WinAFL and Peach Fuzzing Framework. We have decided to choose WinAFL for this analysis, as it is a mutation-based and coverage feedback-driven fuzzing engine. For a detailed overview of AFL usage targeting Linux applications, please refer to one of our blogposts here. For instrumentation, we have employed DynamoRio, a runtime instrumentation framework, executed in block coverage mode.
2. Creating a test harness
Considering all the above, we now must create a Windows GUI application that makes use of the vulnerable GdiPlus.dll library. The final harness looks like this:
Test harness for fuzzing GdiPlus.dll
We start by reading an EMF file provided as an argument. Then we initiate an EmfToWmfBits operation (line 136), which will hopefully trigger the vulnerability. Then, after triggering a Metafile::EmfToWmfBits operation on the EMF file, we also clean the environment by freeing the used components and by closing the opened file.
3. Sample generation
To come up with an EMF file containing an EMR_STRETCHBLT record, we investigated the WinGDI and GDI+ documentation and came up with a simple generator that looks like this:
Generator for an EMF file containing an EMR_STRETCHBLT record
We have also included in the initial corpus EMF files with other record types to increase the coverage and, with it, the chance to find additional crashes.
4. Corpus minimization and coverage
An important step to optimize the fuzzing process is corpus minimization. This process will remove the samples that do not significantly improve the code coverage to prevent wasteful CPU cycles on similar samples. Applying the winafl-cmin.py script, which is a part of the WinAFL repository, to our initial EMF corpus of 32 samples, we obtain 10 significant ones:
Corpus minimization step using winafl-cmin.py script
What we’re trying to do here is to run all the initial test cases through the harness, in DynamoRio’s runtime instrumentation mode, while capturing at each run the reached basic blocks within the library of interest.
Using IDA’s Lighthouse plugin allows us to investigate if our final corpus has a decent coverage:
IDA Lighthouse plugin: Code coverage for GdiPlus.dll
As we can see, our target GdiPlus!GdipEmfToWmfBits is reached just fine, along with a good portion of the library as well.
After 1 day and 21 hours, we have obtained more than 80 unique library crashes:
WinAFL campaign: 1 master and 2 slaves
Triaging them with BugID, there are multiple samples that crash our method of interest:
BugID report: Possible RCE within GdiPlus!DoRotatedStretchBlt
Firing up a WinDbg instance with the crash sample confirms the finding:
WinDbg output: Sample obtained through fuzzing triggering a crash corresponding to CVE-2019-0618: stacktrace (green) and crash analysis provided by the !exploitable WinDbg plugin (red)
In a similar manner, we have obtained library crashes related to CVE-2019-0614, CVE-2019-0618, and CVE-2019-0619. Moreover, we have identified some other interesting library bugs during our fuzzing campaign and reported them to Microsoft.
In effect, we have developed strikes for CVE-2019-0618, CVE-2019-0614, CVE-2019-0618, and CVE-2019-0619, which are available in the latest BreakingPoint strikepack.
LEVERAGE SUBSCRIPTION SERVICE TO STAY AHEAD OF ATTACKS
The Ixia's Application and Threat Intelligence (ATI) Subscription provides bi-weekly updates of the latest application protocols and vulnerabilities for use with Ixia test platforms. The ATI Research Center continuously monitors threats as they appear in the wild. Customers of our BreakingPoint product have access to strikes for different vulnerabilities, allowing them to test their currently deployed security controls’ ability to detect or block such attacks.