FortiGuard Labs Threat Research

A root cause analysis of CVE-2018-0797 - Rich Text Format Stylesheet Use-After-Free vulnerability

By Wayne Chin Yick Low | April 01, 2018

Over the last few months, the Microsoft Security Response Centre (MSRC) has released a number of Windows updates to fix multiple Use-After-Free (UAF) vulnerabilities discovered by FortiGuard Labs. As stated in our previous blog post, we will provide a technical write-up for one of the UAF issues that was rated as critical by MSRC. The issue is assigned to CVE-2018-0797. In this blog post we will share our methodologies in identifying the root cause of the issue, as well as an analysis of the mitigation deployed by Microsoft to address the UAF vulnerability.

Please take note that the following analysis was performed on Microsoft Word 2010 running on Windows 7 32-bit.

Standing on the shoulders of giants - Differential analysis using enhanced AlleyCat, Lighthouse, and BinDiff

Vulnerability researchers should know that security audits are extremely complex, especially for software that has a huge code base. What is even more challenging is that Microsoft Office does not provide debugging symbols, which are helpful in recognizing resolved function names, their parameters, or any local variables. As a result, most of the time it requires more reverse engineering efforts than usual to understand Microsoft Office patches.

Fortunately, there are some convenient tools we can use to help us to reduce some of the challenges that we might encounter when analyzing a large binary file. Without further ado, let’s start our analysis. Please take note that the following differential analysis was performed using wwlib.dll 14.0.7191.5000 and 14.0.7192.5000.

First of all, let’s look at the following BinDiff’s screenshot that compares the patched and unpatched versions of wwlib.dll:

Figure 1: BinDiff wwlib.dll result 14.0.7191.5000 vs 14.0.7192.5000

As you can see from the BinDiff output, there are a lot of updates in the patch. In fact, you can see low confidence level on the diffing result, which indicates that BinDiff could produce false alarm on some of the results from this analysis. In other words, we can’t merely rely on BinDiff to find the patch functions related to our UAF vulnerability. So here are the challenges that we need to solve:

·       Narrow down the functions to the ones that we are interested in

·       Find and analyse the code path that leads us to the vulnerable function

Thanks to on Embedi’s blog, we learned about an excellent IDA Pro script called AlleyCat, developed by by devttys0, which enables IDA Pro to automatically find paths between two or more functions. No doubt, this script can help us to solve our second challenge, but it still has its limitations, which are introduced by the first challenge. A picture is worth a thousand words:

Figure 2: Call-graph for the vulnerable function

The call-graph shown in Figure 2 is generated by taking the following steps using AlleyCat:

·       First, we run our RTF proof-of-concept (POC) file on a vulnerable version of Microsoft Word to trigger the vulnerable function, which is highlighted by a red box

·       From the vulnerable function wwlib_cve_2018_0197, we run AlleyCat View -> Graphs -> Find paths to the current function from … -> Pick FMain -> OK

Obviously, the generated call-graph is complicated, since by default AlleyCat includes all the functions directly and indirectly related to wwlib_cve_2018_0197 starting from the entry point FMain. Our ultimate goal is to find the functions that are related to our POC only. The good news is this can be easily done by manipulating code coverage. Basically, code coverage can be generated using DynamoRio’s DrCov plugin. What we need to do is to use the appropriate command line tool from DynamoRio’s suite, such as drrun.exe, to execute the vulnerable Microsoft Word together with our POC document. As a result, a coverage file will be generated by drrun.exe. We can then take the parser code from the Lighthouse plugin to parse the coverage information generated by DrCov plugin, use this information as a filter, and then apply it to the first call-graph we collected previously. Finally, we get the following refined call-graph:

Figure 3: Refined call-graph for the vulnerable function

As you can see in Figure 3, we have rediced the number of functions that we should investigate. We can even use BinDiff now to determine which updated function(s) address the specific vulnerability. Another benefit of this refined call-graph is that it allows us to focus on only those functions related to our POC. For example, we can quickly identify the RTF parsing routine in functions sub_31B22D39 and sub_31B25BD5 after some back tracing, which save us a lot of analysis time. On top of that, the coverage paintings feature provided by Lighthouse also makes our static analysis easier.

(As a side note, while we took most of the DrCov parser code from Lighthouse, we found a trivial bug that can cause inconsistency on the coverage output in some cases. We subsequently fixed the bug and pushed the updates to Lighthouse upstream. The description of the bug can be found here in case you are interested in learning about the details. However, we are not going to provide the source code for our enhanced AlleyCat here since the integration of DrCov’s parser functionality and the implementation of the functions filter by code coverage into AlleyCat is pretty straight forward.)

Stylesheet control words structure definition

Before we dive into the details of the vulnerability, we need to first understand the RTF stylesheet data structure that is helpful for explaining the underlying vulnerability. As we mentioned previously, since we do not have the debugging symbol for wwlib.dll, we can only recognize its data structure through reverse engineering.

Digging in further, we are able to determine the stylesheet data structure. Let’s say we have an RTF file with the following stylesheet control word defined:

Figure 4: First example of an RTF document with stylesheet control words only

Its raw data can be seen in the following memory dump when viewed using the WinDBG debugger:

Figure 5: Definitions of the stylesheet object header
Figure 6: Arrays of the pointer stylesheet that follow after the header definitions

Based on the output of the memory dump, we know that the styledef control words that we defined in our example RTF are stored as pointers (0x5338768, 0xfdf2768 and 0xfb00768) in memory address 0x1022af60. In short, we can interpret this memory structure as the following C data structure:


struct _strucStyleSheet{

     DWORD dwCountStyles;

     DWORD dwTotalStyles;

     DWORD dwSizeofPtr;

     DWORD dwSizeofHeader;

     DWORD dwUnknown1;

     DWORD dwUnknown2;

     void *pUnknown;

     DWORD dwUnknown3;

     void *pStyleDefs[dwTotalStyles];


From its memory layout, it is easy to understand that the arrays at index 0 to 14 from pStyleDefs are used to store default styledef pointers that are insignificant in this context. Instead, what we are interested in are the arrays from index 15 and above, which typically consist of styledef pointers (\s1, \s2 and \s3) defined in our RTF document (as shown in Figure 4.) We also attempted to reverse engineer the meaning of the raw data of these arrays. Unfortunately, we were not able to fully interpret the complete data structure of pStyleDefs, but its partial definition seemed good enough to help us to understand the underlying vulnerability. So we started to play around with the control words within styledef and modified the first example RTF to become:

Figure 7: Second RTF document example with \sbasedonN control word

Basically, we added the \sbasedon1 control word in the second RTF example. As quoted from the Microsoft RTF specification, the \sbasedonN control word defines the style handle of the style that the current style is based on. In a nutshell, we tell \s2 that it should inherit styles from \s1. We should be able to notice the changes in pStyleDefs[16]. Remember that the first styledef \s1 has array index 15 and the second styledef \s2 has array index 16,  at offset 2 from memory dump:

Figure 8: Data of pStyleDefs[16] after adding the \sbasedonN control word

By analyzing the stylesheet parsing routine, we know that the styledef index can be retrieved from the styledef pointer at offset 2. As shown in Figure 8, the value at offset 2 from address 0xf6e0790 represents the array index of pStyledefs specified in the \sbasedonN control word, which yields 0xF after shifting the value 4 bits to the right (0xF1 >> 4). As you may notice, \sbasedon1 is referring to \s1. Hence it is important to take note that the index to the array pStyleDefs always starts with 0xF, or 15 in decimal, for the first styledef control words such as \sN, \sbasedonN, and \slinkN used in the RTF document.

The root-cause of CVE-2018-0797 UAF vulnerability

UAF refers to a vulnerability that allows an attacker to access memory after it has been freed, which can cause a program to crash, allow the execution of arbitrary code, or even enable full remote code execution.

In fact, it only takes us little time to locate the patch for the CVE-2018-0797 UAF vulnerability – kudos to our enhanced AlleyCat script! After we have narrowed down the functions that we want to look into, we can use BinDiff to take a glance at the updates and notice that the one updated function where the crash occurred, wwlib_cve_2018_0797, has had a code block added.

Figure 9: Added code block in the updated wwlib_cve_2018_0797 function

We were lucky this time because the fix was found on the same function where the crash occurred. Based on our past experience, the patches are usually located in different functions and most of the time the researchers would need to spend some time trying to find and locate it. Of course, sometimes it also depends on the nature of the vulnerability. Anyway, our findings reinforce our confidence that we can reveal the root cause of the UAF from this function.

Upon analyzing the patched function, we realized that the purpose of the added code block is to ensure that the loop always gets the updated pStyleDefs pointer. When \sbasedonN is encountered, the pStyleDefs pointer is updated to allocate more space in order to store additional information for the styledef it should be inherited from. Under the hood, Microsoft Word calls the heap reallocation function with a larger buffer size to replace the pStyleDefs pointer. To make things clear, there is no dangling pointer that happened here. But before the fix, the old pStyleDefs pointer was freed whenever heap reallocation took place. Hence, when a function attempts to link one style with another, as demonstrated at (2) in Listing 1, when it returns to the caller it still holds the old pStyleDefs pointer. The UAF occurs when pStyleDefs is dereferenced somewhere within the vulnerable function later, as shown at (1). As a workaround, the vulnerable function makes sure that the pStyleDefs pointer is updated accordingly by adding the code block that always returns the updated pStyleDefs pointer to the caller, as shown in the highlighted code in Listing 1.

Listing 1: Pseudocode of wwlib_cve_2018_0797 after the patch

The next question is how we trigger wwlib_cve_2018_0797. After some deeper analysis, we realize that the RTF parser initializes and maintains a reference table for the styledef index. For instance, the example RTF in Figure 7 has a styledef reference table that looks like Table 1:

Table 1: Styledef reference table for RTF in Figure 7

Here’s the deal; there is a conditional check in the RTF parser that determines the integrity of the current styledef index, as well as the index to styledef from the reference table. In fact, it’s really challenging to determine the purpose of this reference table integrity check, but it’s presumably that when the current styledef index does not match the one initialized in the styledef reference table, the parser routine attempts to restructure the stylesheet data structure, which eventually leads to wwlib_cve_2018_0797. There are multiple ways to provoke this mismatch; the typical way is by defining N=0 in the \sN control word together with the additional \stylesheet control word in the RTF file:

Figure 10: Example RTF document with additional stylesheet control word

The example RTF in Figure 10 results in new styledef reference table looks like:

Table 2: Styledef reference table for RTF in Figure 10

Please take note that the reference index now starts with 0, as shown in Table 2, because of the definition of \s0 in the updated example RTF. The caveat is that when the RTF parser parses the second \stylesheet control word, it has the reference index 0, which is supposed to be the reference index for \s0, for current styledef (\s4), instead of reference index 4. When the RTF parser queries the reference table to obtain the styledef index for the current styledef, the styledef index 0xF will be returned, but in fact the current styledef index (\s4) has the index value of 0x14. Because of this discrepancy, it can lead us to the vulnerable function.

In summary, the UAF can be triggered when:

·       Multiple \stylesheet control words are defined and one of the style control words must forcibly cause the RTF parser to initialize reference index 0 in the reference table.

·       \sbasedonN control word triggers heap reallocation to expand the style attributes stored in the data structure. The data structure expansion causes the initial pointer of the style object to become freed and invalid while it’s still being dereferenced in the vulnerable function, in order to access some data from the invalid pointer.


In conclusion, in this follow-up blog post, we shared our methods for analyzing a large binary using multiple open source tools that help us to reduce our analysis time from weeks to days. While understanding the implementation of this open source codes, we also discovered some issues and limitations, so we contributed our updates back to the open source community by proposing fixes and features to these open source tools. And finally, we provided some insights on the root cause of the vulnerability. Based on our analysis, we assumed that the duplicated stylesheet control word is allowed by design; that’s why Microsoft didn’t attempt to mitigate the misplacement of the styledef index in the styledef reference table when the second stylesheet control word was used. However, it’s probably worth further investigation to see if it could lead to other potential vulnerabilities.

Signing off,

The FortiGuard Lion Team

Sign up for our weekly FortiGuard intel briefs or to be a part of our open beta of Fortinet’s FortiGuard Threat Intelligence Service.