Maddie Stone

The Basics

Disclosure or Patch Date: 9 March 2021

Product: Microsoft Internet Explorer


Affected Versions: KB4601319 and previous

First Patched Version: KB5000802

Issue/Bug Report: N/A

Patch CL: N/A

Bug-Introducing CL: N/A

Reporter(s): yangkang(@dnpushme) & huangyi(@C0rk1_H) & Enki

The Code


<!-- saved from url=(0014)about:internet -->

String.prototype.repeat = function (size) { return new Array(size + 1).join(this) }

var ele = document.createElement('element')
var attr1 = document.createAttribute('attribute')
attr1.nodeValue = {
    valueOf: function() {

ele.setAttribute('attr2', 'AAAAAAAA')


Exploit sample:

Did you have access to the exploit sample when doing the analysis? Yes

The Vulnerability

Bug class: Use-after-free

Vulnerability details:

A value of an attribute is able to be freed twice.

When calling removeAttributeNode, mshtml!CElement::ie9_removeAttributeNodeInternal is called. ie9_removeAttributeNodeInternal first finds the 2 indices for the node entry in the attribute array for the element. (Attribute nodes have 2 entries in the attribute array whereas non-node attributes only have one.) The use-after-free occurs because there is a user-controlled callback between the calculating the indices and when they are used. The backing store buffer can be changed during this callback and the code doesn’t verify that the index is still valid.

In the case of the above crash POC, the backing store buffer of the array is cleared using clearAttributes in the callback. mshtml!CElement::clearAttributes clears the attributes backing array by deleting/freeing the first element in the array and memmoving the following entries into that space. This means that when clearAttributes is finished and the code path returns to removeAttributeNodeInternal, we have now filled the freed space with whatever attribute was at the end of the attribute array and that attribute has also been freed. This can be a pointer to the user-controlled string object. In this case the “use” is a double free on the String object that holds the value for the second attribute added to the element.

Patch analysis:

The patch modified CAttrArray::Destroy. At the beginning of the function, it checks that the index passed in as an argument of the attribute to be deleted is less than the number of elements in the AttrArray. If it's not, a ReleaseAssert is called.

Thoughts on how this vuln might have been found (fuzzing, code auditing, variant analysis, etc.):

It seems equally plausible that this vulnerability was found with a fuzzer or by manually auditing places where CBase::GetIntoBSTRAt/VariantChangeTypeSpecial is called. That is the call which triggers the callback.

(Historical/present/future) context of bug:

In January 2021, Google TAG published that North Korean hackers were targeting security researchers. 10 days later on February 4th, Enki published that some of their researchers had been targeted by the same actor, but with a .mht file. This exploit was contained within that file. Microsoft patched on March 9th, 2021.

The Exploit

(The terms exploit primitive, exploit strategy, exploit technique, and exploit flow are defined here.)

Exploit strategy (or strategies):

The double free is used to have two objects, one array of Dictionary items and one BSTR, allocated to the same block of memory on the heap. These two objects are able to be allocated to the same spot because a BSTR has the structure of: first 4 bytes is the length and the rest is teh string data. The array of dictionary items is a VARIANT struct so it has the type in the first four bytes. The type is (VT_ARRAY | VT_VARIANT) = 0x200C. The BSTR also has a length of 0x200C, making them both valid objects.

The exploit uses this type confusion to leak the addresses of objects stored in the Dictionary by reading the bytes at the equivalent indices in the BSTR. It then uses the Dictionary to read and write to different memory values using a trick described in the "Exploitation, Part 1: From Arbitrary Write to Arbitrary Read" section of this blog post by Simon Zuckerbraun, including creating an ArrayBuff whose address begins at 0x00000000 and its length is 0xFFFFFFFF, yielding an arbitrary read-write primitive.

The exploit bypasses Control Flow Guard (CFG) by overwriting the export address table entry in rpcrt4.dll for ___guard_check_icall_fptr with KiFastSystemCallRet.

Exploit flow:

Two objects are allocated to the same block of memory thanks to the double free. This is used to first leak the address of objects using the VBArray objects for dereferencing. Then the exploits overwrites the length and starting address of an ArrayBuff to have an arbitrary read-write primitive from 0x00000000 - 0xFFFFFFFF.

Known cases of the same exploit flow:

  • The technique for leaking object addresses using teh Scripting.Dictionary/VBArray object is documented by Simon Zuckerbraun in this blog post.
  • Getting arbitrary read-write via changing the length of an Array is very common, but this blog post by Elliot Cao is roughly similar to this exploit.

Part of an exploit chain? Unknown

The Next Steps

Variant analysis

Areas/approach for variant analysis (and why):

  • Audit anywhere that a callback occurs between where an index is calculated and when it's used.
  • Fuzz HTML attributes, potentially with a tool like Domato

Found variants: N/A

Structural improvements

What are structural improvements such as ways to kill the bug class, prevent the introduction of this vulnerability, mitigate the exploit flow, make this type of vulnerability harder to exploit, etc.?

Ideas to kill the bug class:

  • Verify the state of the objects being operated on after the callback. In this case it could be simplified to don't use indices that were calculated prior to a callback.
  • Internet Explorer is now considered “legacy” software. Removing it from being accessible by default in the Windows operating system would reduce the attack surface.

Ideas to mitigate the exploit flow:

  • Remove Internet Explorer from being enabled by default.

Other potential improvements:

0-day detection methods

What are potential detection methods for similar 0-days? Meaning are there any ideas of how this exploit or similar exploits could be detected as a 0-day?

  • To detect the type confusion, look for BSTRs of lengths that equal types of common VARIANT types: 0x200C (VT_ARRAY | VT_VARIANT), 0x400C (VT_BYREF | VT_I4). This would likely have lots of false positives, but maybe it could be further refined.

Other References