FortiGuard Labs Threat Research

A 14-day Journey through Embedded Open Type Font Fuzzing

By Wayne Chin Yick Low | October 19, 2017

Introduction

One of our daily routines as researchers here at FortiGuard Labs is to write and maintain our internal fuzzers to help us more effectively find potential vulnerabilities on different software products. We have a range of such tools, from highly sophisticated algorithms to some dumb fuzzers that run 24/7 to find potential issues on Microsoft Office suites. Even those give us surprises from time to time, even though they are not cutting edge fuzzers. In this blog post we would like to share how we discovered multiple Embedded Open Type (EOT) font vulnerabilities by using a combination of dumb and intelligent open source fuzzers.

Background

EOT fonts are a compact form of OpenType fonts which are typically compressed using MicroType® Express font compression technology. According to W3C, this is a modified version of the LZW compression algorithm, and its compression and decompression algorithm is well documented on the W3C site. EOT fonts are also used to protect the copyrighted font files embedded in, for instance, websites, PowerPoint documents, and other third party software that utilize embedded fonts.

Perhaps due to its compactness, the EOT file format is not very complicated. If you are fan of file format reverser, you might probably find this 010 Editor EOT template useful, and a detailed description of each field can be found here, which can come in very handy when it comes to creating your own EOT font utility.

Day 0: CVE-2017-8691 - Express Compressed Fonts Remote Code Execution Vulnerability

We came across our first EOT font vulnerability when our dump fuzzer, dubbed PPTFuzz, found an interesting test case that crashed Microsoft PowerPoint. This is shown in List 1, below, which is an excerpt of the crash log produced by PPTFuzz. However, what really caught our attention was the offending instruction code found in the  t2embed.dll module. Later, we confirmed this DLL is responsible for loading EOT fonts after checking one of its export functions: TTLoadEmbeddedFont from MSDN.

!exploitable 1.6.0.0
HostMachine\HostUser
Executing Processor Architecture is x86
Debuggee is in User Mode
Debuggee is a live user mode debugging session on the local machine
Event Type: Exception
Exception Faulting Address: 0x785ff000
First Chance Exception Type: STATUS_ACCESS_VIOLATION (0xC0000005)
Exception Sub-Type: Read Access Violation

Faulting Instruction:6d9b7b32 mov dl,byte ptr [eax]

Basic Block:
    6d9b7b32 mov dl,byte ptr [eax]
       Tainted Input operands: 'eax'
    6d9b7b34 cmp dl,0fbh
       Tainted Input operands: 'dl'
    6d9b7b37 jne t2embed!ttembedfontfromfilea+0x14b42 (6d9b7b88)
       Tainted Input operands: 'ZeroFlag'

Exception Hash (Major/Minor): 0x618a64bf.0x89ba1ef6

 Hash Usage : Stack Trace:
Major+Minor : t2embed!TTEmbedFontFromFileA+0x14aec
Major+Minor : t2embed!TTEmbedFontFromFileA+0x15077
Major+Minor : t2embed!TTEmbedFontFromFileA+0x15893
Major+Minor : t2embed!TTEmbedFontFromFileA+0x102f7
Major+Minor : t2embed!TTEmbedFontFromFileA+0x116a0
Minor       : t2embed!TTEmbedFontFromFileA+0x11cc9
Minor       : t2embed!TTEmbedFontFromFileA+0xf871
Minor       : t2embed!TTEmbedFontFromFileA+0xf45c
Minor       : t2embed!TTEmbedFontFromFileA+0x2f03
Minor       : t2embed!TTEmbedFontFromFileA+0x5e0
Minor       : t2embed!TTEmbedFontFromFileA+0x238e
Minor       : t2embed!TTLoadEmbeddedFont+0x1a1
Minor       : gfx!Ordinal841+0x9128
Minor       : gfx!Ordinal841+0x9257
Minor       : gfx!Ordinal841+0x9b1b
Minor       : ppcore!DllGetLCID+0x5c676d
Minor       : ppcore!DllGetLCID+0x5307cd
Minor       : ppcore!DllGetLCID+0x117575
Minor       : ppcore!DllGetLCID+0xd891
Minor       : ppcore!DllGetLCID+0xf6f1
Minor       : ppcore!DllGetLCID+0xfc18
Minor       : ppcore!DllGetLCID+0xf900
Minor       : ppcore!DllGetLCID+0xf8c4
Minor       : ppcore!DllGetLCID+0xb0cc
Minor       : ppcore!PPMain+0x1be56
Minor       : ppcore!DllGetLCID+0x1fd9d3
Minor       : ppcore!DllGetLCID+0xd5560
Minor       : ppcore!PPMain+0xb16
Minor       : ppcore!PPMain+0x57
Minor       : powerpnt+0x1186
Minor       : kernel32!BaseThreadInitThunk+0x12
Excluded    : ntdll!RtlInitializeExceptionChain+0xef
Excluded    : ntdll!RtlInitializeExceptionChain+0xc2
Instruction Address: 0x000000006d9b7b32 

List 1: Excerpt of crash log

Before we decided to dive deeper into EOT font fuzzing, we first wanted to understand the root cause of this vulnerability. Unfortunately, we didn’t find any existing utility that could extract the embedded font from inside an office document, so we decided to write a simple python script to do the job for us. After we had successfully extracted the font, we did a quick analysis against the mutated EOT font and concluded that mutated compressed font data could cause an out-of-bound read, like the one demonstrated in List 1, during the decompression operation performed by t2embed.dll. Based on our analysis, we classified the issue as an information leak vulnerability; however, according to Microsoft it could potentially lead to remote code execution.

Nevertheless, we didn’t want to spend too much time looking into its exploitability as we wanted to dig deeper to look for other low hanging fruit in the EOT font parser engine.

Day 1-7: Preparing corpus & EOT Loader + WinAFL

The AFL fuzzer should be no stranger to those who have been doing fuzz testing. The fact is that AFL is more effective for open source fuzzing using compiler instrumentation compared to binary fuzzing using QEMU, which has some performance overhead. But keep in mind that AFL is mostly useful for programs running on the Linux platform. Recently, Ivan Fratric successfully ported AFL to Windows, which is known as WinAFL, that allows you to fuzz test Windows binaries. However, my experience told me that WinAFL does not work very well for complex binaries, such as Microsoft Office suites, in large part because it consists of global states that could break the persistent mode or in-memory fuzzing operation.

The effectiveness of WinAFL is in large part due to its persistent mode mechanism, but users need to ensure that the input file feed to WinAFL is successfully closed in order to achieve a faster execution time. This can be achieved easily if we have good control of the binary. For instance, patching the binary will not affect the stability of the original program or recompiling the binary from the source code. Hence, EOT font seems to be a good target to be fuzzed using WinAFL by creating a simple utility that we called loadeot, which will leverage the TTLoadEmbedFont API function in order to load the font. Since we adapted most of the code from Adobe’s WebKitAir, it didn’t take much effort to write our custom EOT loader utility. Because it’s the only such tool we know of, we decided to open source this utility in case someone else might find it useful. The source code of loadeot can be found in our Github’s repository.

We also crawled and downloaded EOT fonts from the Internet while simultaneously extracting some of the EOT fonts from our previously collected PowerPoint documents. After a week, we were able to gather over 10,000 EOT files. Before we started fuzzing them, we made use of winafl-cmin.py, a ported version of afl-cmin (kudos to Axel Souchet!), to minimize the corpus to around hundred test cases.

Day 8-14: From mild out-of-bound reads to severe buffer overflows

Over the course of a day of fuzzing we got a couple of crashes from the WinAFL fuzzer running with our custom EOT loader. We also got a number of out-of-bound read crashes that typically lead to trivial information leakage upon successful exploitation. However, the most interesting crash that caught our attention is a test case found on the latest build of Windows 10 x86 1607. So we decided to look into this particular test case and perform some additional analysis. Please take note that the following analysis is based on t2embed.dll version 6.1.7601.17514 on Windows 7 x86.

Analysis of CVE-2017-11763

As usual, we checked the call-trace and its crash context:

(a94.aec): Access violation - code c0000005 (first chance)
First chance exceptions are reported before any exception handling.
This exception may be expected and handled.
eax=0067c550 ebx=0064def0 ecx=000001ca edx=00000000 esi=0064f970 edi=0067dfd0
eip=7736a069 esp=0030f3c8 ebp=0030f3d0 iopl=0         nv up ei pl nz na pe nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00010206
msvcrt!__ascii_strnicmp+0xa7:
7736a069 660f7f5f30      movdqa  xmmword ptr [edi+30h],xmm3 ds:0023:0067e000=????????????????????????????????
0:000> kb
ChildEBP RetAddr  Args to Child              
0030f3d0 7736a00b 0067c550 0064def0 0000ff80 msvcrt!__ascii_strnicmp+0xa7
0030f400 7736ac05 0067c550 0064def0 0000fff7 msvcrt!_VEC_memcpy+0x52
0030f414 73265bf1 000a0000 0064dee8 00000030 msvcrt!_VEC_memcpy+0xb4
0030f430 73267c81 0067c548 0064dee8 0000ffff t2embed!T2malloc+0x20
0030f4a0 73263697 000f5cb0 00000101 000b38c8 t2embed!ApplyNameChangeToNameRecords+0x4bc
0030f4d8 732654bb 000205a4 0030f610 000b38c8 t2embed!T2EnableEmbeddingForFacename+0x327
0030f600 732624c6 00000001 0030fbc8 00000004 t2embed!T2LoadEmbeddedFont+0x26d
0030fa98 00f4721c 0030fbcc 00000001 0030fbc8 t2embed!TTLoadEmbeddedFont+0x1a1
0030fbe0 00f4aa89 00000002 000b24e0 000b2560 loadeot!main+0x34c [e:\office\d_drive\sourcecodes\_font\ttf2eot\loadeot\loadeot.cpp @ 430]
0030fc28 760def8c 7ffd8000 0030fc74 77db367a loadeot!__tmainCRTStartup+0xfe [f:\dd\vctools\crt\crtw32\startup\crt0.c @ 255]
0030fc34 77db367a 7ffd8000 1480f72f 00000000 kernel32!BaseThreadInitThunk+0xe
0030fc74 77db364d 00f3dff5 7ffd8000 00000000 ntdll!__RtlUserThreadStart+0x70
0030fc8c 00000000 00f3dff5 7ffd8000 00000000 ntdll!_RtlUserThreadStart+0x1b

List 2: The call-trace and its crash context

The crash context told us that an out-of-bound write to an invalid address caused a memory access violation. By looking at the function name, we could tell that the faulty code was located at t2embed!ApplyNameChangeToNameRecords. After some reverse engineering, we confirmed the exact location of the offending code, as well as its root cause. Another interesting finding was that we realized that this function is only triggered if szWinFamilyName is provided to the API function TTLoadEmbeddedFont, which indicates that an alternative font name will be used rather than using the existing font name specified in the EOT file.

Upon analysing t2embed!ApplyNameChangeToNameRecords, we found that the issue resided in parsing the TTF naming record in which the length of the name record that was retrieved from the EOT file is not properly sanitized by the parser.

// Start parsing array of name records
while ( 1 )
  {
    if ( currentNameRec >= (unsigned __int16)TotalNameRecord )
    {
	// Parsing done
    }
    pCurrNameRecord = (NAME_RECORD *)(a1 + 0x1C * (unsigned __int16)count);
    nameLen = pCurrNameRecord->nameLen;        // Length controllable from EOT file
    if ( (unsigned __int16)nameLen > 0xFFFFu ) // First sanity check that can be bypassed
    {
      v39 = 11;
      goto Exit_Invalid_NameLen;
    }

List 3: A code snippet of name records parsing

 

As shown in the highlighted code in List 3, the parser stored the font name length to a variable - we named it nameLen - which is controllable by adversaries. When looking at its pseudocode, we believe the length bound check as shown in the highlighted code is insufficient because nameLen is used later to calculate the buffer length for the new font name, which eventually results in an integer overflow, as demonstrated in the following code snippet:

NameRecordNewName = T2MemAlloc((unsigned __int16)(wFamilyFontLen + nameLen - OffsetString));   // An underflow buffer will be allocated due to 16-bit integer overflow
            pCurrNameRecord->pNewName = NameRecordNewName;
            if ( !NameRecordNewName )
              goto LABEL_87;
            WrappedCopyNewName((int)wszWinFamilyName, NameRecordNewName, WinFamilyFontLen >> 1); 
            memcpy(
              (void *)(WinFamilyFontLen + pCurrNameRecord->pNewName),
              (const void *)((unsigned __int16) OffsetString + pCurrNameRecord->pStringData),
              pCurrNameRecord->nameLen - (unsigned __int16) OffsetString); // Out-of-bound write of attacker’s controlled length up to 0xFFFF. While attacker can also control its data from NameRecord->pStringData

List 4: Integer overflow leads to buffer overwrite in parser engine

In fact, there are two possible ways to trigger a buffer overflow, either in WrappedCopyNewName or memcpy. If the OffsetString is not NULL, the allocated underflow buffer will not be able to fit the new font name, and thus the buffer overwrite will take place in WrappedCopyNewName, which is a wrapper function that calls memcpy to copy the new font name into NameRecordNewName. From the attacker’s point of view, the exploitation will be more convenient if the buffer overflow occurs after WrappedCopyNewName is called, which is the direct call to memcpy with the attacker’s controlled nameLen. What’s even worse is that the OffsetString is also controllable from the EOT file, thereby making it trivial for an attacker to exploit this vulnerability. Nevertheless, this is a classic buffer overwrite due to an integer overflow, and it affects all versions of Windows including the latest build of Windows 10, as far as we have tested. Microsoft Security and Response Center (MSRC) later classified this vulnerability as critical because it is very likely to be exploited, along with the fact that it could also lead to remote code execution on the latest version of Windows 10.

 Conclusion

We urge Windows users to consistently apply Microsoft patches to keep themselves protected.

For Fortinet’s customers, you can use the corresponding IPS signatures to protect your environment:

MS.Windows.Express.Compressed.Fonts.Remote.Code.Execution (CVE-2017-8691) and MS.Windows.Graphics.EOT.File.Parsing.Code.Execution (CVE-2017-11763).

Until next time, stay safe!           

Signing off

-== 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.