FortiGuard Labs Threat Research

An Inside Look at CVE-2017-0199 – HTA and Scriptlet File Handler Vulnerability

By Wayne Chin Yick Low | June 04, 2017

FortiGuard Labs recently came across a new strain of samples exploiting the CVE-2017-0199 vulnerability. This vulnerability was fixed by Microsoft and the patch was released in April 2017. Due to its simplicity, it can be easily exploited by attackers. It has also been found in-the-wild by other vendors. We have also blogged about some samples recently found in spear phishing attack.

While there are plenty of articles discussing this vulnerability, most of them are intended for technical readers and primarily focus on how to create proof-of-concept (POC) for the vulnerability. If you are looking for an easy-to-understand article, we found a high-level overview of the vulnerability in this article. In this blog post we will share our insight on the patch for CVE-2017-0199 and share some findings we discovered on the exploit samples.

Inside CVE-2017-0199 Patch 

It is a malware analyst’s instinct to perform black-box testing by running unknown samples in a restricted environment to observe the sample’s behavior in order to determine if the sample is malicious or not. The process of analyzing exploit samples is very similar, but we sometimes also test the sample on the latest application, in this case the Microsoft Office suite, to see if the patch works as expected. When I saw the new CVE-2017-0199 samples, I ran them on my test machine, and to my surprise, I noticed that the exploit sample still managed to successfully download the payload from its remote server. I further verified and confirmed that the payload was saved into a temporary Internet Explorer folder, based on the network traffic. Even if the payload is no longer triggered on the latest Office suite, which indicates that the patch works normally, I decided to try to understand how it works for the sake of curiosity.

We are lucky that there are not many components involved in the CVE-2017-0199 patch so it makes our analysis become much easier. Basically, the patch is divided into 2 components:

  • OLE32.dll, 6.1.7601.23714 on Windows 7 x86
  • MSO.dll, 14.0.7180.5002 on Microsoft Office 2010 on x86

We first made a comparison between the patched and unpatched OLE32.dll binary. Based on the differing results, we discovered there are quite a number of changes, as shown in Figure 1:

Figure 1: OLE32.dll 6.1.7601.23714 (left) VS 6.1.7601. 23392 (right)

The highlighted entry in Figure 1 caught my attention at first because the primary name (5th column) does not match the secondary name (7th column) even though it has a high similarity rate. This may indicate that it is a newly added function. To check further, we can use the code cross-reference feature in IDA-Pro to get the caller functions to FilterActivation:

Figure 2: FilterActivation xref under IDA-Pro

In Figure 2 we can see that FilterActivation can be triggered from the internal COM functions, namely ICoGetClassObject and ICoCreateInstance, which are wrapped by higher-level COM APIs like CoCreateInstance and CoGetClassObject that are used for COM object instantiation. After examining the comparison result for one of these internal COM functions, as shown in Figure 3, we can confirm that FilterActivation is a new function and is probably responsible for tackling the logical issue incurred by the CVE-2017-0199 vulnerability. Next ,we will dive into this routine to validate our hypothesis.

Figure 3: Comparison result of ICoCreateInstance

Fortunately, it didn’t take us very long time to analyze this routine as the implementation is quite straightforward, and the code becomes clearer after fixing some of the data types and structure definitions. Here is its pseudocode:

Figure 4: FilterActivation implementation

In short, the code figure above gets an input parameter clsid from the caller function and passes it into a handler function defined in g_ActvationFilter. g_ActivationFilter is a global variable of type IActivationFilter, which is initialized by MSO.dll during COM initialization via CoRegisterActivationFilter. Remember that this DLL is one of the components in the CVE-2017-0199 patch. The following pseudocode briefly illustrates the operation of the filter initialization in MSO.dll. It simply gets the API address of CoRegisterActivationFilter from OLE32.dll and calls it later with g_pActivationFilter as its argument:

Figure 5: Pseudocode to show activation filter initialization routine in MSO.dll

We knew that g_pActivationFilter is a pointer to a variable of type IActivationFilter, which is stored at g_ActvationFilter, which is the global variable discussed in Figure 4. Again, we are able to cross-reference this variable to locate the handler function through static analysis. This eventually leads us to the destination of the handler function, namely mso_IActivationFilterHandleActivation:

Figure 6: Static analysis to locate handler function

The handler function reveals that we are following the right path, as we can see that some filter operations will take place here. One remarkable thing we saw is that an access deny code will be returned if the clsid consists of either:

  • CLSID_SCRIPTLET = {06290bd3-48aa-11d2-8432-006008c3fbfc}
  • CLSID_HTA = {3050f4d8-98b5-11cf-bb82-00aa00bdce0b}

Figure 7: Filter routine in handler function

Bear in mind that the CLSID_HTA is a global unique identifier formed by 32 hexadecimal characters representing the Microsoft HTML Application or file with file extension .hta,.This can be abused by attackers to achieve remote code execution, especially in Microsoft Office documents. This can happen when the URL Moniker is instructed by an Office document to download an embedded resource in the document from a remote server.

By design, URL Moniker will always honor server-supplied Multipurpose Internet Mail Extension (MIME) type, as documented in MSDN, and the downloaded file will later be loaded and executed by mshta.exe if “application/hta” MIME type is provided by a remote server. Under the hood, the HTA COM object will be instantiated first during the binding operation, and CoCreateInstance is called by URLMON.dll in the process of instantiation, which is initiated by the URL Moniker. However, before the COM object can be initialized, in the latest version of OLE32.dll the CLSID of the COM object will be inspected by the FIlterActivation function, and it will fail gracefully in the event of CLSID_HTA being instantiated, as shown in Figure 7.

But what about CLSID_SCRIPTLET? Why does the handler function also block it from the Microsoft Office document? At first, we could not make a POC trigger the code path that initializes the scriptlet COM object until we stumbled upon Matt Nelson’s tweet. It turns out that his POC can be reproduced reliably, but that it requires user interaction by clicking on a hyperlink preceded with “script:” or “scriptlet:” in a URL defined in the document.


// Parse the moniker name "script:xxxxxx OR scriptlet:xxxxxx"


0:000> kb

ChildEBP RetAddr  Args to Child             

001e92fc 766cce60 159ceff8 1627cfc8 001eaa4c scrobj!ComMonikerFactory::ParseDisplayName

001e9354 766ccf2c 1627cfc8 001eaa4c 001e9384 ole32!FindClassMoniker+0xf8 [d:\w7rtm\com\ole32\com\moniker2\cmonimp.cxx @ 1850]

001e938c 7543655a 1627cfc8 001eaa4c 001ea484 ole32!MkParseDisplayName+0xbb [d:\w7rtm\com\ole32\com\moniker2\cmonimp.cxx @ 1467]

001ea414 71b803c9 1627cfc8 001eaa4c 001ea484 urlmon!AppDataFolderList::GetPackageDependencyStateForIUri+0x17f3f

001ea444 71b76421 00000001 106a6e74 00000000 hlink!HrParseDisplayNameEx+0x197

001ea498 71b7681a 00000001 001eaa4c 00000000 hlink!HLNK::HrSetStringReference+0x91

001ea4b0 61c08bd2 162daf28 00000001 001eaa4c hlink!HLNK::SetStringReference+0x25

WARNING: Stack unwind information not available. Following frames may be wrong.

001ea4cc 6181c47f 16298fd0 00000001 001eaa4c mso!Ordinal10017+0x2aa1

001eba9c 61c1f384 16298fd0 00000000 00000000 mso!Ordinal8417+0x28a

001ebac0 697b4415 16298fd0 00000000 00000000 mso!Ordinal2959+0x1c

001ebb10 697b5377 00000000 00000000 0f5d0948 wwlib!DllGetLCID+0x7edf8f

001ebbc4 697b7190 0f5d0948 00000000 0000008e wwlib!DllGetLCID+0x7eeef1

001ebbec 69394c1d 69ca4400 0000008e 0000007e wwlib!DllGetLCID+0x7f0d0a

001eda34 6938facc 0000008e 0000007e 00000000 wwlib!DllGetLCID+0x3ce797

001eda64 692ebe3c 048c72b0 00000201 00000009 wwlib!DllGetLCID+0x3c9646

001edaf0 68dc4acd 00350666 00000201 00000009 wwlib!DllGetLCID+0x3259b6

001edb30 757ec4b7 00350666 00000201 00000009 wwlib!DllGetClassObject+0xf471

001edb5c 757ec5b7 68dc4a8c 00350666 00000201 USER32!InternalCallWinProc+0x23

001edbd4 757ecbe9 00000000 68dc4a8c 00350666 USER32!UserCallWinProcCheckWow+0x14b

001edc34 757ecc40 68dc4a8c 00000000 001edc54 USER32!DispatchMessageWorker+0x357

001edc44 68e1426d 69c9e630 69c9e630 001edc6c USER32!DispatchMessageW+0xf

001edc54 68e13e05 69c9e630 757e2b1d 69c9e630 wwlib!GetAllocCounters+0x4da95

001edc6c 68e13d4b 00000001 1311cfe4 12f10f8c wwlib!GetAllocCounters+0x4d62d

001edc94 68e12cf0 68db517d 7728cebc 68db0000 wwlib!GetAllocCounters+0x4d573

001efdf4 2fb91c68 2fb90000 00000000 0121ffd1 wwlib!GetAllocCounters+0x4c518

001efe18 2fb91ec2 2fb90000 00000000 0121ffd1 winword!wdGetApplicationObject+0x63a

001efea8 7728ef8c 7ffd8000 001efef4 7713367a winword!wdGetApplicationObject+0x894

001efeb4 7713367a 7ffd8000 366f1751 00000000 kernel32!BaseThreadInitThunk+0xe

001efef4 7713364d 2fb92045 7ffd8000 ffffffff ntdll!__RtlUserThreadStart+0x70

001eff0c 00000000 2fb92045 7ffd8000 00000000 ntdll!_RtlUserThreadStart+0x1b

Listing 1: A call stack to show the execution flow when a hyperlink is clicked in a document

Figure 8: The hyperlink in the document will be parsed by ParseDisplayName function when it was clicked

In a nutshell, scriptlet is also known as Windows Script Component, which is designed to execute script language like Javascript, VBScript, and PerlScript. It is able to execute script code placed within an XML-format text file. After some reverse-engineering, we discovered that when a hyperlink is triggered, the scriptlet’s parser will recognize the file associated with the URL as a scriptlet file based on its prefix, causing the Windows Script Component, SCROBJ.dll, to be loaded and instantiated by HLINK.dll in order to execute the script file. The call stack below demonstrates the flow of executing the scriptlet file associated with the hyperlink:

0:000> kb

ChildEBP RetAddr  Args to Child             

0027a25c 6a720899 00000000 00000000 14bf8ff0 jscript!COleScript::ExecutePendingScripts

0027a278 6cb6831f 14bf0de8 00000001 14baae28 jscript!COleScript::SetScriptState+0x51

0027a288 6cb68464 14bdefd8 14bdefe4 00000000 scrobj!ScriptEngine::Activate+0x1a

0027a2a0 6cb699d3 00000000 1420ffd0 00000000 scrobj!ComScriptlet::Inner::StartEngines+0x6e

0027a2f0 6cb6986e 00000000 015dffb0 0027a320 scrobj!ComScriptlet::Inner::Init+0x156

0027a300 6cb6980b 015dffb0 14b9cf10 00000000 scrobj!ComScriptlet::New+0x3f

0027a320 6cb697d0 14b9cf10 00000000 00000000 scrobj!ComScriptletConstructor::CreateScriptletFromNode+0x26

0027a340 6cb737e2 015dffb0 00000000 00000000 scrobj!ComScriptletConstructor::Create+0x4c

0027a360 6cb74545 00000000 71a6245c 0027a460 scrobj!ComScriptletFactory::CreateScriptlet+0x1b

0027a380 7671b53d 015d1ff0 1417efc8 1420ffd0 scrobj!ComScriptletMoniker::BindToObject+0x4d

0027a3b4 71a6a858 138a4fd0 1417efc8 00000000 ole32!CCompositeMoniker::BindToObject+0x105 [d:\w7rtm\com\ole32\com\moniker2\ccompmon.cxx @ 1104]

0027a3f0 71a65ab7 1390ff78 00000001 14158fe0 hlink!HLBC::GetObjectA+0x143

0027a468 65638cb8 00000000 00000000 00000000 hlink!HLNK::Navigate+0x2ae

WARNING: Stack unwind information not available. Following frames may be wrong.

0027a488 71a68352 13b1efd0 00000000 00000000 mso!Ordinal10017+0x2b87

0027a4b0 6524c724 13b1efd0 00000000 00000000 hlink!HlinkNavigate+0xc0

0027ba88 6564f384 13b1efd0 00000000 00000000 mso!Ordinal8417+0x52f

0027baac 66684415 13b1efd0 00000000 00000000 mso!Ordinal2959+0x1c

0027bafc 66685377 00000000 00000000 0e9bc948 wwlib!DllGetLCID+0x7edf8f

0027bbb0 66687190 0e9bc948 00000000 00000087 wwlib!DllGetLCID+0x7eeef1

0027bbd8 66264c1d 66b74400 00000087 0000007d wwlib!DllGetLCID+0x7f0d0a

0027da20 6625facc 00000087 0000007d 00000000 wwlib!DllGetLCID+0x3ce797

0027da50 661bbe3c 03d152b0 00000201 00000009 wwlib!DllGetLCID+0x3c9646

0027dadc 65c94acd 0008094e 00000201 00000009 wwlib!DllGetLCID+0x3259b6

0027db1c 757ec4b7 0008094e 00000201 00000009 wwlib!DllGetClassObject+0xf471

0027db48 757ec5b7 65c94a8c 0008094e 00000201 USER32!InternalCallWinProc+0x23

0027dbc0 757ecbe9 00000000 65c94a8c 0008094e USER32!UserCallWinProcCheckWow+0x14b

0027dc20 757ecc40 65c94a8c 00000000 0027dc40 USER32!DispatchMessageWorker+0x357

0027dc30 65ce426d 66b6e630 66b6e630 0027dc58 USER32!DispatchMessageW+0xf

0027dc40 65ce3e05 66b6e630 757e2b1d 66b6e630 wwlib!GetAllocCounters+0x4da95

0027dc58 65ce3d4b 00000001 12accfe4 12ad0f8c wwlib!GetAllocCounters+0x4d62d

0027dc80 65ce2cf0 65c8517d 7728cebc 65c80000 wwlib!GetAllocCounters+0x4d573

0027fde0 2f1d1c68 2f1d0000 00000000 0008ffd1 wwlib!GetAllocCounters+0x4c518

0027fe04 2f1d1ec2 2f1d0000 00000000 0008ffd1 winword!wdGetApplicationObject+0x63a

0027fe94 7728ef8c 7ffdd000 0027fee0 7713367a winword!wdGetApplicationObject+0x894

0027fea0 7713367a 7ffdd000 45b88b21 00000000 kernel32!BaseThreadInitThunk+0xe

0027fee0 7713364d 2f1d2045 7ffdd000 ffffffff ntdll!__RtlUserThreadStart+0x70

0027fef8 00000000 2f1d2045 7ffdd000 00000000 ntdll!_RtlUserThreadStart+0x1b

Listing 2: A call-stack to show the execution flow when WSC executing a script

Enabling a hyperlink that can lead to code execution within a document file may sound unnerving, even if it requires the user interaction of clicking on a hyperlink. This arbitrary script file code execution inside a Microsoft Office document reminds me of the ScriptBridge ActiveX vulnerability used in CVE-2013-1331 that also allowed an adversary to automatically execute embedded Javascript code. However the caveat here is that, so far, we haven’t found a way to load and execute the scriptlet file automatically from an Office document.

To summarize, Microsoft has blacklisted 2 COM objects from Office document that could lead to remote code execution. Even if it is a logical issue from a URL Moniker, it will be kept untouched because it is one of the useful features in Microsoft Office. In my opinion, the patch is pretty straightforward and can be scaled easily if there are other, yet to be discovered COM objects that need to be blacklisted in the future.

Obfuscated RTF evading signature based detection

As we mentioned at the beginning of this blog post, we encountered a couple of relatively new (at the time of discovery) samples exploiting CVE-2017-0199, which seem to be interesting. The reason why is because these samples show yet another example how persistent malware authors are able to evade anti-virus detection. In fact, our friends at FireEye previously shared plenty of practical examples on how RTF malware attempts to evade signature based detection. We believe this is not the first RTF malware utilizing this trick. However, considering the detection rate is still low and some vendors do not have a precise detection name for these samples (probably due to this evasion trick), I think it’s worth sharing here in order to create awareness for other security vendors who need to create signature-based detections to protect their customers.

Figure 9: Sample #1

In the first sample we encountered, we realized that a control word can be included within “\objdata”. As shown in Figure 9, “\deftabN” has segmented the hex-string value contained in “\objdata”. Based on RTF specification, the “\deftabN” should follow a decimal value. In other words, if we discard the decimal value followed by the “\deftab” control word, the final highlighted hex-string can be translated as:

Figure 10: Sample #1: De-obfuscated string

The hex-string segmentation, like in Figure 9, does not, obviously, affect the Word RTF parser, but it could break some static based signature detection if it looks for malicious URLs, typically one of the patterns used in signatures by analysts, to HTA a file exploiting the vulnerability in CVE-2017-0199. However, what really interesting in this sample is the use of the “\objemb” control word instead of “\objlink” used in most of the POC for CVE-2017-0199. Based on our experiment, it appears that the “\objemb” control word could trigger the same vulnerability.

In the second sample, we saw it successfully bypass most of the static detection based on the fact that the detection rate is still very low. Most of the anti-virus engines contain an RTF parser that attempts to scan and look for an OLE data stream embedded in the RTF file. Commonly, the OLE data stream can be determined by locating the control word “\objdata”. Again, according to RTF specification, a standard control word such as “\objdata” should be preceded by control symbol “\*” (backslash asterisk). In this particular sample, it replaces control symbol “*” with an arbitrary character. For Word RTF parser, i the unknown control symbol is skipped, and it continues parsing the data stream followed by the control word “\objdata”. We are not sure how this trick could affect the RTF parser of other anti-virus engines without looking at the source code or reverse engineering the parser, but we assumed this could be the root cause of the bypass as we do not find any other notable obfuscation in this sample.


In the process of clarifying our questions raised from the exploit sample we encountered, we realized that the patch for CVE-2017-0199 also includes the fix for scriptlet COM remote code execution in Microsoft Office, which is not widely mentioned in the online articles talking about CVE-2017-0199. In addition, we also shared some of the tricks used by RTF malware to evade signature based detection. Understanding these evasion techniques is helpful to us to improve our detection capability to protect our customers through IPS detection MS.Office.RTF.File.OLE.autolink.Code.Execution and AV detection MSOffice/CVE20170199.A!tr. Likewise, we also recommend Windows users install the latest updates from Microsoft Office and always keep Windows OS updated.

-= FortiGuard Lion Team =-


Sample #1:



Sample #2: