作者: nine8
QQ  : 279933462
杂记: http://hi.baidu.com/tapeout


MS11-015微软补了Media Player的2个漏洞,其中库预加载的可以导致远程执行代码,但是第二个感觉只是导致后面解码时的错误不会导致远程执行代码:

     1). DLL库预加载的, 通过构造相关的dll文件,涉及到dvr-ms, mpg和mtv文件类型的打开时可能会加载,导致远程执行代码。

     2). Media Player在播放dvr-ms文件时,会调用Stream Buffer Engine来处理dvr-ms文件流, 在处理的时候sbe.dll中的问题导致可能存在的处理错误,但是个人分析感觉不能
          被利用远程执行代码。(另外也可能是我分析的错误,或是出现问题的地方定位就不对)。


由于没有POC,以及对触发问题的dvr-ms文件格式不熟悉,相关东西都是现看的,所以分析中可能会有很多问题,如果大家发现,还请给小弟多多指出,感谢!

下面对MS11-015的DVR-MS 漏洞 CVE-2011-0042, 做简要的分析。对于CVE-2011-0032,由于不涉及WinXP, VM里跑vista或win7很不顺畅,这里就先没有分析.


  0x01  相关概要
--------------------------

      == 0x11  调试环境:  VMware7.0 + en_windows_xp_professional_with_service_pack_3_x86

      == 0x12  漏洞编号:  MS11-015, CVE-2011-0042

      == 0x13  问题文件:  c:\windows\system32\sbe.dll

      == 0x14  名词摘要: (摘录自微软MSDN)
           
                        1) . 流缓冲引擎 (SBE)。利用 SBE,应用程序可以搜索、暂停以及录制实时视频流,而不会中断该流。实时内容和录制内容之间的转换为无缝转换。现在,
                             SBE 支持 MPEG-2 视频和数字视频 (DV) 源,捕获速率高达每秒 30 兆比特 (Mbps)。

                         2).  在 Microsoft Windows XP Media Center Edition 中,Microsoft 已引入了 *.dvr-ms 文件格式。类似于 *.asf 格式,*.dvr-ms 文件增强了允
                             许创建关键PVR 的功能,其中包括时光平移、实时暂停以及同时录制和播放。


  0x02  问题分析
---------------------------

代码:
.text:4EE52F2F public: static long __stdcall DShowWMSDKHelpers::RecoverNewMediaType(struct INSSBuffer3 *, struct _AMMediaType * *) proc near
.text:4EE52F2F
.text:4EE52F2F var_4= dword ptr -4
.text:4EE52F2F pINSSBuffer3= dword ptr  8                                   ; 第一个参数指向INSSBuffer3
.text:4EE52F2F ppAMMediaType= dword ptr  0Ch                          ; 第二个参数指向AMMediaType指针
.text:4EE52F2F
.text:4EE52F2F   mov   edi, edi
.text:4EE52F31   push  ebp
.text:4EE52F32   mov   ebp, esp
.text:4EE52F34   push  ecx
.text:4EE52F35   push  ebx
.text:4EE52F36   push  esi
.text:4EE52F37   mov   ebx, [ebp+pINSSBuffer3]
.text:4EE52F3A   push  edi
.text:4EE52F3B   mov   eax, [ebx]
.text:4EE52F3D   lea   ecx, [ebp+var_4]
.text:4EE52F40   push  ecx                                                              ; Buffer大小
.text:4EE52F41   push  0                                                                 ; 将接收属性内容的Buffer指向NULL, 获取所需大小空间
.text:4EE52F43   sub   esp, 10h
.text:4EE52F46   mov   edi, esp
.text:4EE52F48   mov   esi, offset _INSSBuffer3Prop_DShowNewMediaType    : 这里是下面要获取的属性GUID
.text:4EE52F4D   movsd                                                                 ; DShowNewMediaType
.text:4EE52F4E   movsd                                                                  :1135BEB7-3A39-47BA-D998-69Eb006BC715
.text:4EE52F4F   movsd
.text:4EE52F50   push  ebx                                                            ; INSSBuffer3 *
.text:4EE52F51   movsd
.text:4EE52F52   call  dword ptr [eax+2Ch]            ; 通过INSSBuffer3的GetProperty获取DShowNewMediaType属性内容
.text:4EE52F55   mov   esi, eax                               ; 这里也是通过逆向分析才推出是调用的GetProperty,之前并不知道功能。
.text:4EE52F57   test  esi, esi
.text:4EE52F59   jl    loc_4EE5300F

上面的代码主要是通过GetProperty函数,通过将接收字符的buffer指向NULL,来获取所需的Buffer大小,关于GetProperty的实现细节,后面内容会有详细的逆向分析,
以及简单猜测INSSBuffer3结构和查找GUID内容的过程。GetProperty的COM接口如下:

   
代码:
HRESULT ( STDMETHODCALLTYPE *GetProperty )( 
            INSSBuffer3 * This,
            /* [in] */ GUID guidBufferProperty,
            /* [out] */ void *pvBufferProperty,
            /* [out][in] */ DWORD *pdwBufferPropertySize);

代码:
.text:4EE52F5F   push  [ebp+var_4]                                      ; 这里为通过上面获取的所需存储GUID内容的空间大小
.text:4EE52F62   call  operator new(uint)                                         ; 开辟内存空间, 之后出现问题的地方会涉及到这片空间内容 !!!
.text:4EE52F67   test  eax, eax
.text:4EE52F69   pop   ecx
.text:4EE52F6A   mov   [ebp+pINSSBuffer3], eax                                                         
.text:4EE52F6D   jz    loc_4EE5300

下面再次通过INSSBuffer3的GetProperty获取DShowNewMediaType GUID对应的内容,并存入上面开辟的动态空间。


代码:
.text:4EE52F73   mov   ecx, [ebx]
.text:4EE52F75   lea   edx, [ebp+var_4]
.text:4EE52F78   push  edx                                                                ; 所需接收空间大小,单位byte
.text:4EE52F79   push  eax                                                                ; 接收buffer指向上面开辟的空间
.text:4EE52F7A   sub   esp, 10h
.text:4EE52F7D   mov   edi, esp
.text:4EE52F7F   mov   esi, offset _INSSBuffer3Prop_DShowNewMediaType      ; GUID
.text:4EE52F84   movsd
.text:4EE52F85   movsd
.text:4EE52F86   movsd
.text:4EE52F87   push  ebx
.text:4EE52F88   movsd
.text:4EE52F89   call  dword ptr [ecx+2Ch]                                         ; NSSBuffer3 GetProperty
.text:4EE52F8C   mov   esi, eax
.text:4EE52F8E   test  esi, esi
.text:4EE52F90   jl    short loc_4EE52FFF

代码:
.text:4EE52F92   push  48h             ; cb                                   ;  0x48 bytes, 为AMMediaType结构大小
.text:4EE52F94   call  ds:CoTaskMemAlloc(x)                                        ; 开辟AMMediaType结构所需的0x48 bytes空间
.text:4EE52F9A   test  eax, eax
.text:4EE52F9C   mov   ebx, [ebp+ppAMMediaType]
.text:4EE52F9F   mov   [ebx], eax
.text:4EE52FA1   jz    short loc_4EE5
其中定义媒体格式的AMMediaType结构定义如下:

代码:
typedef struct _MediaType {        // total size: 0x48            
  GUID majortype;                // offset 0x00
  GUID subtype;                // offset 0x10
  BOOL bFixedSizeSamples;            // offset 0x20
  BOOL bTemporalCompression;          // offset 0x24
  ULONG lSampleSize;              // offset 0x28
  GUID formattype;                // offset 0x2C
  IUnknown *pUnk;                    // offset 0x3c
  ULONG cbFormat;                // offset 0x40    <------- 可选数据大小,该成员会涉及到该漏洞的产生
  [size_is(cbFormat)] BYTE *pbFormat;              // offset 0x44    <------- 指向可选数据内容 
} AM_MEDIA_TYPE;

下面将查询到GUID: DShowNewMediaType的内容的前0x48 bytes存入AMMediaType结构的空间中

代码:
.text:4EE52FA3   mov   esi, [ebp+pINSSBuffer3]   ;  指向new开辟的空间
.text:4EE52FA6   push  12h
.text:4EE52FA8   mov   edi, eax                                        ; 指向CoTaskMemAlloc开辟的空间
.text:4EE52FAA   pop   ecx
.text:4EE52FAB   rep movsd
.text:4EE52FAD   mov   eax, [ebx]
.text:4EE52FAF   mov   ecx, [eax+40h]                             ; 这里通过可选数据大小判断是否有可选数据,如果有下面会继续开辟空间
.text:4EE52FB2   test  ecx, ecx
.text:4EE52FB4   jbe   short loc_4EE

下面就是问题出现的地方了

代码:
.text:4EE52FB6   push  ecx             ; cb                                    ; 根据AMMediaType的cbFormat大小,开辟对应的空间
.text:4EE52FB7   call  ds:CoTaskMemAlloc(x)                                                                            
.text:4EE52FBD   mov   ecx, [ebx]
.text:4EE52FBF   mov   [ecx+44h], eax                                                 ; 将指针存入AMMediaType结构的pbFormat成员
.text:4EE52FC2   mov   ebx, [ebx]
.text:4EE52FC4   mov   edi, [ebx+44h]                                                                                
.text:4EE52FC7   test  edi, edi
.text:4EE52FC9   jz    short loc_4EE

根据cbFormat的大小,将pbFormat的数据从GUID Buffer中赋值到pbFormat指向的空间,因为这里是根据cbFormat的大小,而并没有判断
GUID Buffer中从单次采样中实际或到属性内容的大小,那么如果构造畸形cbformat,使cbFormat + AMMediaType的48字节大于GUID Buffer中从GUID后的
QWORD size得到的实际所需大小(这里的QWORD Size是通过分析GetProperty得知的下面会具体分析),为GUIID后的第一个
QWORD.那么将会出现超过实际QWORD size提供大小后,后面所存入到pbFormat的额外数据信息将是从New堆后的无效数据,从而可能导致后面encdec时的
错误,但这里因为是从小复到大所以不会产生堆溢出,所以个人感觉这个漏洞,不会被利用远程执行代码。当然前面也提到可能我分析的不对,定位不准确,没有找到
真正问题的地方。


代码:
.text:4EE52FCB   mov   ecx, [ebx+40h]
.text:4EE52FCE   mov   esi, [ebp+pINSSBuffer3]
.text:4EE52FD1   mov   eax, ecx
.text:4EE52FD3   shr   ecx, 2
.text:4EE52FD6   add   esi, 48h
.text:4EE52FD9   rep movsd
.text:4EE52FDB   mov   ecx, eax
.text:4EE52FDD   and   ecx, 3
.text:4EE52FE0   rep movsb
后面就是一些内存释放和返回。这里就不分析了。

下面分析下涉及到的INSSBuffer3的GetProperty方法,因为通过其获取的GUID DShowNewMediaType内容和如何返回Buffer大小,会影响问题形成的buffer大小信息。见2楼

  • 标 题:答复
  • 作 者:nineB
  • 时 间:2011-04-01 01:47:02

下面是INSSBuffer3 GetProperty的实现

代码:
.text:7D814231
.text:7D814231 loc_7D814231:
.text:7D814231 mov     edx, [ebx]
.text:7D814233 push    ebx                                     ;
.text:7D814234 call    dword ptr [edx+3Ch] ;         // 这里会调用函数sub_7D8143C0
.text:7D814237 mov     esi, eax                              // 传入INSSBuffer3*, GUID, Buf,
.text:7D814239 test    esi, esi                                 // 和BufSize4个参数
.text:7D81423B jge     short loc_7D814284
下面的函数主要是先判断是否为一个GUID属性,如果不是则进行查找。

代码:
.text:7D8143C0 sub_7D8143C0 proc near
.text:7D8143C0
.text:7D8143C0 p_arg2= dword ptr -18h
.text:7D8143C0 p_GUID= byte ptr -14h
.text:7D8143C0 SecCookie= dword ptr -4
.text:7D8143C0 arg_0= dword ptr  8
.text:7D8143C0 arg_1= byte ptr  0Ch
.text:7D8143C0 arg_2= dword ptr  1Ch
.text:7D8143C0 arg_3= dword ptr  20h
.text:7D8143C0
.text:7D8143C0 mov     edi, edi
.text:7D8143C2 push    ebp
.text:7D8143C3 mov     ebp, esp
.text:7D8143C5 sub     esp, 18h
.text:7D8143C8 mov     ecx, [ebp+arg_2] ; ecx = arg2
.text:7D8143CB mov     eax, dword_7D933080
.text:7D8143D0 mov     edx, [ebp+arg_3] ; edx = arg_3
.text:7D8143D3 push    ebx
.text:7D8143D4 push    esi
.text:7D8143D5 push    edi
.text:7D8143D6 lea     esi, [ebp+arg_1]
.text:7D8143D9 lea     edi, [ebp+p_GUID]
.text:7D8143DC movsd
.text:7D8143DD movsd
.text:7D8143DE movsd
.text:7D8143DF movsd
.text:7D8143E0 mov     [ebp+p_arg2], ecx ; var_18 = ecx = arg_2
.text:7D8143E3 push    4
.text:7D8143E5 pop     ecx             ; ecx = 4
.text:7D8143E6 lea     edi, [ebp+p_GUID]
.text:7D8143E9 mov     esi, offset dword_7D87F750    ; 这里是另一个GUID值
.text:7D8143EE xor     ebx, ebx        ; ebx = 0
.text:7D8143F0 repe cmpsd
.text:7D8143F2 mov     [ebp+SecCookie], eax
.text:7D8143F5 mov     eax, [ebp+arg_0] ; eax = arg0
.text:7D8143F8 jnz     short loc_7D81442D      ; 如果不是调用函数查找
下面函数传入4个参数,其中第一个参数应该是指向链表结构,offset相对于INSSBuffer3为0x3c
剩下的3个参数为GUID, Buf, BufSize

代码:
.text:7D81442D
.text:7D81442D loc_7D81442D:           ; arg3
.text:7D81442D push    edx
.text:7D81442E push    [ebp+p_arg2]    ; var_18 = arg2
.text:7D814431 lea     esi, [ebp+p_GUID]
.text:7D814434 sub     esp, 10h
.text:7D814437 mov     edi, esp
.text:7D814439 movsd
.text:7D81443A movsd
.text:7D81443B movsd
.text:7D81443C lea     ecx, [eax+3Ch]  ; ecx = arg0+0x3c
.text:7D81443F movsd
.text:7D814440 call    sub_7D815170

下面是sub_7D815170, 主要是查找通过遍历链表结构成员查找GUID属性,如果找到GUID则获
取其后面的AMMediaType数据信息

代码:
.text:7D8151A5
.text:7D8151A5 loc_7D8151A5:
.text:7D8151A5 push    [ebp+var_18]
.text:7D8151A8 mov     ecx, ebx        ; ecx = ebx = arg0
.text:7D8151AA call    sub_7D814F74                     ;   遍历查找GUID
.text:7D8151AF push    4
.text:7D8151B1 pop     ecx             ; ecx = 4
.text:7D8151B2 lea     edi, [ebp+dwBufForID]
.text:7D8151B5 mov     esi, eax
.text:7D8151B7 xor     edx, edx        ; edx = 0
.text:7D8151B9 repe cmpsd
.text:7D8151BB jz      short loc_7D8151E6

代码:
.text:7D8151EA push    [ebp+p_arg3]
.text:7D8151ED mov     ecx, ebx        ; ecx = ebx = arg0
.text:7D8151EF push    [ebp+p_arg2]
.text:7D8151F2 push    0
.text:7D8151F4 push    [ebp+var_18]
.text:7D8151F7 call    sub_7D8150DB    ; 如果找到GUID属性,获取其数据内容
.text:7D8151FC jmp     short loc_7D8151D7
.text:7D8151FC sub_7D815170 endp
下面就不详细写了,其中一个是当输入的参数指向NULL的时候,会返回BufSize为GUID后面的第
一个QWORD Size里的值,然后返回0.

另外2个是根据IndexVector来在链表结构中查找对应的GUID属性内容,下面是我简单的推测的
结构。可能不准或不对。有些是中间的推导过程记录的,后面可能变了,但没改过去。

sub_7D814E60:

+-------------------------------------+    <--- arg0
|           DWORD             |
+-------------------------------------+    <--- arg + 0x04: 标记该元素单元的地址范围
|node offset addr refr link head|          
+-------------------------------------+    <--- arg0 + 0x08
|            | bit0 |
+-------------------------------------+    <--- arg0 + 0x0c
| ID Address ( if +0x8 bit0=1 )   |
+-------+---------+--------+--------+    <--- arg0 + 0x10
|      | b7-b4  | b7-b0 | b7-b0 |                  
+-------+---------+--------+----------+    <--- arg0 + 0x14
|      .........         |
|                 |
| 如果从 arg0+0x10, 0x11, 0x12中 |
|的bit Flag检索值为1,则:         |
| arg0+0x14+arg1*4 指向的DW存 |<--- 如果从Flag的bit中检索值为1,则ID的地址从这个范
|有ID的Address.           |
|  Bit Flag为1的优先级大于offset      |
|bit0为1的优先级.           |        
|      .........         |
+----------------------------------------+    <--- arg0 + 0x64
|  next node start address       |
+----------------------------------------+    <--- arg0 + 0x68
|prev node end offset value addr |    
+----------------------------------------+


其中:
arg0+0x10的 bit7 到 bit0: 分别对应地址 arg0+0x14 到 arg0+0x33  (8个DWORD)
arg0+0x11的 bit7 到 bit0: 分别对应地址 arg0+0x34 到 arg0+0x53  (8个DWORD)
arg0+0x12的 bit7 到 bit4: 分别对应地址 arg0+0x54 到 arg0+0x63  (4个DWORD)



sub_7D814DBE:

// 函数: sub_7D814DBE(BaseAddr, BitOffset);
//
// 功能: 根据参数arg1指定的offset和bit Mask,来返回对应的Bit值是1还是0
//
//    +-------------------------------+    <--- arg0
//    |        DWORD        |
//    +-------------------------------+    <---- arg + 0x04
//    |        DWORD        |      
//    +-------------------------------+    <--- arg0 + 0x08
//    |        DWORD        |
//    +-------------------------------+    <--- arg0 + 0x0c
//    |        DWORD        |
//    +-------+-------+-------+-------+    <--- arg0 + 0x10
//    |    | Flags | Flags  | Flags  |                      |
//    +-------+-------+-------+-------+
//
//    
//    real pointer = arg0 + 0x16 + offset(Byte Offset, Bit Offset)
//
//
//    DWORD arg1 format: (用于指定offset的, Byte offset和bit offset)
//
//    +------------------------------------+-----------------------------------+    
//    | byte offset of checked byte number | bit offset of checked byte number |
//    +------------------------------------+-----------------------------------+
//    bit31      -----        bit4      ------        bit0
//
//    == Bit31 - Bit4 : (byte offset of checked number)
//      
//      指定要被测试的Bit所在Byte的地址偏移量
//        
//      
//    == Bit3 - Bit0 : (bit offset of checked number)
//      
//      指明要被测试的Bit的Byte偏移基础上的bit偏移量
//
//      0 - check byte bit-7 value
//      1 - check byte bit-6 value
//      2 - check byte bit-5 value  
//      3 - check byte bit-4 value
//      4 - check byte bit-3 value
//      5 - check byte bit-2 value
//      6 - check byte bit-1 value
//      7 - check byte bit-0 value
//
//    所以被测试的Bit的偏移过程:
//      通过Byte Offset确定其所在byte的地址, 然后同过bit offset定位到具体的是哪个bit
//
//
// 返回: 如果对应的bit为1, 则返回1; 为0, 则返回0;
//     也就是返回对应bit的值.
//

代码:
BYTE BitMask[8] = {0x80, 0x40, 0x20, 0x10, 0x08, 0x04, 0x02, 0x01};

// ChkBitValue
sub_7D814DBE(
  arg0,    // Base Addr
  arg1)    // BitOffset
{
  BYTE BitMaskIndex;
  BYTE ByteNum;
  BYTE BitFlag;

  ByteNum = (*( arg0 + 0x10 + (arg1 >> 3) )) & 0x7;

  BitMaskIndex = arg1 & 0x7;

  BitFlag = ByteNum & BitMask[BitMaskIndex];

  return( (BitFlag == 0) ? 0 : 1 );  
}