Microsoft Office Web Components. CVE-2009-1136 分析

粗略一看,这个漏洞似乎跟OWC10.DLL跟没有什么关系,细跟Evaluate、msDataSourceObject貌似很难发现有什么不妥的代码,但它的确就是Evaluate的某个BUG导致漏洞被利用,(似乎跟msDataSourceObject没啥关系)。

Evaluate的声明如下:
        HRESULT _Evaluate(
                        [in] VARIANT Expression, 
                        [out, retval] VARIANT* Result);
它的主要功能是计算一个表达式的值,并将计算的结果送到*Result,它要求VARIANT型的Expression.VARTYPE必须为VT_BSTR,如果不为VT_BSTR则会把输入原样送到输出变量*Result中
造成漏洞的主要代码是Evaluate中的
.text:38AC76D2 66 83 7D 0C 08          cmp     [ebp+arg_4], 8
.text:38AC76D7 74 11                   jz      short loc_38AC76EA
.text:38AC76D9 8D 75 0C                lea     esi, [ebp+arg_4]
.text:38AC76DC 8B 7D 1C                mov     edi, [ebp+pvarg]
.text:38AC76DF A5                      movsd
.text:38AC76E0 A5                      movsd
.text:38AC76E1 A5                      movsd
.text:38AC76E2 A5                      movsd
.text:38AC76E3 33 C0                   xor     eax, eax
.text:38AC76E5 E9 0C 03 00 00          jmp     loc_38AC79F6

意思差不多就是如下:
if (Expression.VARTYPE !=  VT_BSTR)
{
*Result = Expression;
goto exit;
}
else
{
//执行表达式的计算
...........................
//将运算结果送入*Result
*Result = xxxxxxxxxx;
}
表面上看逻辑似乎没有什么问题。

再看看漏洞利用脚本是怎么利用该函数的:
e.push(1);
e.push(2);
e.push(0);
e.push(window);
for(i=0;i<e.length;i++)
{
    for(j=0;j<10;j++)
    {
        try
        {
            obj.Evaluate(e[i]);
        }
        catch(e)
        {
        }
    }
}
window.status=e[3] +'';

Evaluate有一次会传入window对象,循环10次,上面说过如果传入的参数不为VT_BSTR的话,Evaluate会将输入原样送到输出,window对象的类型为IDISPATCH类型,所会会执行*Result = Expression;操作,如若把window对象也换成数字或字符串的话,漏洞不会被利用,问题已经很明显了,COM规范里有说过:"当把一个非空接口指针写到局部变量中时、当被调用方返回一个非空接口指针作为函数的实际结果时,都要增加接口的引用计数" 

但是,为什么不增加引用计数会造成如此严重漏洞?

用工具查找到Evaluate地址后,观察传入window对象时的栈信息
0012DFC8   770F73D0  RETURN to OLEAUT32.770F73D0
0012DFCC   02890110
0012DFD0   00390009
0012DFD4   0012E43C
0012DFD8   012014B0 <-----window对象
0012DFDC   0012E41C
0012DFE0   0012E034 <-------用来指向返回值


      HRESULT _Evaluate([in] VARIANT Expression, 
                       [out, retval] VARIANT* Result);
对应栈中数据,一个THIS指针(1个DWORD),一个VARIANT结构(4个DWORD),一个VARIANT指针(一个DWORD)

Window对应的VARIANT结构信息中
VARTYPE =9 (VT_DISPATCH)
pdispVal = 012014B0
这个pdispVal指向的是一个TEAROFF_THUNK结构(很像一个对象,有虚函数表),在WIN2K源码中可以找到




struct TEAROFF_THUNK
{
    void *      papfnVtblThis;     // Thunk's vtable
    ULONG       ulRef;             // Reference count for this thunk.
    DWORD       unused;            // BUGBUG delete when can be compiled and tested on ALPHA
    void *      pvObject1;         // Delegate other methods to this object using...
    void *      apfnVtblObject1;   // ...this array of pointers to member functions.
    void *      pvObject2;         // Delegate methods to this object using...
    void *      apfnVtblObject2;   // ...this array of pointers to member functions...
    DWORD       dwMask;            // ...the index of the method is set in the mask.
}
第一次对window对象evalute时pdispVal指向的数据为

012014B0  70 C2 F1 7C 05 00 00 00 CC EF C8 7C 30 0F 20 01  p埋|...田0 
012014C0  28 49 C9 7C 50 8F 20 01 B4 F2 C8 7C 07 00 00 00  (IP?打...

其中偏移0X04处的数据为rlRef也就是引用计数,这时为5 (该值为JS脚本中对window对象循环evaluate的最小值)

每执行完一次evaluate之后,oleaut32.dll会以Evaluate函数的返回值VARIANT* Result作为参数调用 VariantClear,该函数会检测参数类型,如果为VT_DISPATCH的话会调用它的Release函数,Release函数对应于0X7CF1C270(papfnVtblThis)的0x08偏移位置的数据,带着符号看该地址的话,函数名为PlainRelease,该函数的代码也可以从WIN2K中找到。

说说PlainRelease的功能(只说和漏洞相关的重点),它将传入对象的引用计数减1,也就是上面的TEAROFF_THUNK的ulRef减1,判断是否为0,如果为0,则调用InterlockedExchangePointer将该TREAOFF_THUNK指针存入静态变量s_pvCache1中,同时会判断原s_pvCache1的值,如果不为0,则调用InterlockedExchangePointer将原s_pvCache1的值存入静态变量s_pvCache2中,也会判断原s_pvCache2的值,如果为不为0则调用MemFree来释放该原s_pvCache2指向的0X20大小的TEAROFF_THUNK,这里的s_pvCache1和s_pvCache2为什么要用在这里?(猜测是缓存用的,避免不停的分配这0X20大小的数据,MSHTML和JSCRIPT里面有很多这样的TEAROFF_THUNK结构)

当执行五次obj.Evaluate(window)后window对象的引用计数被减为0,则会执行上面说的操作:"将window对象对应的TEAROFF_THUN结构指针存入s_pvCache1中",就这样,好端端的window对象,引用计数被无辜的减到了0,虽然引用计数被减为0,但是它对应的TEAROFF_THUNK结构还是存在的,没有被释放,当下次要用TEAROFF_THUNK结构时,它会被"废物"重利用,何时会被利用?

在漏洞利用脚本中有这么一条“window.status=e[3] +'';”,它会触发先前被缓存到s_pvCache1的TEAROFF_THUNK被再利用,功能存在于CreateTearOffThunk,该函数在WIN2K源码中也可以找到。

说说CreateTearOffThunk(只说和漏洞相前的重点),它会检查s_pvCache1或s_pvCache2
是否为0,如果都为0的话会MemAlloc一个TEAROFF_THUNK结构,随后会根据参数来填充TEAROFF_THUNK结构。

当window.status=e[3] +'';执行时,首先会取查询window接口,会调用CWindow__PrivateQueryInterface于是就进入CreateTearOffThunk函数,这时先前被缓存的TEAROFF_THUNK指针0X012014B0被会再利用,TEAROFF_THUNK的apfnVtblObject1(偏移0X10)会被置为0X7CC8EFA4(XP SP2 IE6 MSHTML VERSION:6.0.2900.2180)

<mshtml. CWindow__PrivateQueryInterface> 
7CCD7A1E       8BFF                 mov     edi, edi

 8BFF                 mov     edi, edi
7CCD7A20            55                   push    ebp
7CCD7A21            8BEC                 mov     ebp, esp
7CCD7A23            53                   push    ebx
7CCD7A24            8B5D 10              mov     ebx, dword ptr [ebp+10]
7CCD7A27            56                   push    esi
7CCD7A28            8B75 0C              mov     esi, dword ptr [ebp+C]
7CCD7A2B            33D2                 xor     edx, edx
7CCD7A2D            8913                 mov     dword ptr [ebx], edx
7CCD7A2F            8B06                 mov     eax, dword ptr [esi]
7CCD7A31            B9 26442C33          mov     ecx, 332C4426
7CCD7A36            3BC1                 cmp     eax, ecx
7CCD7A38            57                   push    edi
7CCD7A39            0F87 21700000        ja      7CCDEA60
7CCD7A3F            0F84 3BD00600        je      7CD44A80
7CCD7A45            B9 B1F65030          mov     ecx, 3050F6B1
7CCD7A4A            3BC1                 cmp     eax, ecx
7CCD7A4C            0F86 FC2C0200        jbe     7CCFA74E
7CCD7A52            2D CFF65030          sub     eax, 3050F6CF
7CCD7A57            0F84 E5120A00        je      7CD78D42
7CCD7A5D            83E8 0D              sub     eax, 0D
7CCD7A60            0F84 ED910200        je      7CD00C53
7CCD7A66            2D 09010000          sub     eax, 109
7CCD7A6B            0F85 BE120A00        jnz     7CD78D2F
7CCD7A71            BF 5C42CB7C          mov     edi, 7CCB425C
7CCD7A76            6A 04                push    4
7CCD7A78            59                   pop     ecx
7CCD7A79            33C0                 xor     eax, eax
7CCD7A7B            F3:A7                repe    cmps dword ptr es:[edi], dword pt>
7CCD7A7D            0F85 4E700000        jnz     7CCDEAD1
7CCD7A83            52                   push    edx
7CCD7A84            53                   push    ebx
7CCD7A85            52                   push    edx
7CCD7A86            68 A4EFC87C          push    7CC8EFA4 <-----这个值指向函数表
7CCD7A8B            FF75 08              push    dword ptr [ebp+8]
7CCD7A8E            E8 EAC4FFFF          call    <CreateTearOffThunk>



接着再对window.status赋值一下,这样会触发一个GetIDsOfNames的操作和invoke的操作,在IDISPATCH可以提供接口函数给脚本来调用,脚本不知道函数的址址,而直接给出函数名来调用,最终会转入COM系统中由IDISPATCH的GetIDsOfNames获取指定函数的ID,然后根据获取的ID来调用Invoke,最终完成脚本所要执行的操作。
当要获取status的ID时会执行到JSCRIPT.DLL的
75BD2E67     FF75 18              push    dword ptr [ebp+18]
75BD2E6A     8B45 0C              mov     eax, dword ptr [ebp+C]
75BD2E6D     FF75 14              push    dword ptr [ebp+14]
75BD2E70     8B08                 mov     ecx, dword ptr [eax]
75BD2E72     FF75 10              push    dword ptr [ebp+10]
75BD2E75     50                   push    eax
75BD2E76     FF51 1C              call    dword ptr [ecx+1C] ; mshtml.7CCFA138

Push eax会把0X012014B0压入(THIS指针)

ECX+1C的代码如下,主要是从0X012014B0偏移0x10取apfnVtblObject1,然后从apfnVtblObject1(7CC8EFA4) 偏移0x1c处取指针,最终jmp ecx
7CCFA138         8B4424 04           mov     eax, dword ptr [esp+4]
7CCFA13C         50                  push    eax
7CCFA13D         F740 1C 80000000    test    dword ptr [eax+1C], 80
7CCFA144         0F85 ABA60800       jnz     7CD847F5
7CCFA14A         83C0 0C             add     eax, 0C
7CCFA14D         8B08                mov     ecx, dword ptr [eax]
7CCFA14F         894C24 08           mov     dword ptr [esp+8], ecx
7CCFA153         8B48 04             mov     ecx, dword ptr [eax+4]
7CCFA156         8B49 1C             mov     ecx, dword ptr [ecx+1C]
7CCFA159         58                  pop     eax
7CCFA15A         C740 20 07000000    mov     dword ptr [eax+20], 7
7CCFA161         FFE1                jmp     ecx

可是由于0X012014B0先前引用计数被减0过,引发了后面CreateTearOffThunk对它的0x10处的apfnVtblObject1修改,apfnVtblObject1早已不是先前的apfnVtblObject1,而现在却要使用apfnVtblObject1来索引它偏0X1C的函数地址,apfnVtblObject1被改为0X7CC8EFA4,0X7CC8EFA4的结构如下:


7CC8EFA4  7CCF9CA0  mshtml.7CCF9CA0
7CC8EFA8  7CCD4129  mshtml.7CCD4129
7CC8EFAC  7CCD4165  mshtml.7CCD4165
7CC8EFB0  7CD33D21  mshtml.7CD33D21
7CC8EFB4  7CCFA0C9  mshtml.7CCFA0C9
7CC8EFB8  7CE14F2A  mshtml.7CE14F2A
7CC8EFBC  D48A6EC6 <-----------------------这里开始存放的是IID CLSID_HTMLWindow2
7CC8EFC0  11CF6A4A <---------------------现在jmp ecx会跳到这个地址去
7CC8EFC4  4544A794 
7CC8EFC8  00005453


就这样,一JMP ECX,就触发了漏洞,eip直接奔向了11CF6A4A ,而该地址早已经被heap spray给占领了,后面就一堆木马被下载下来了。


漏洞原因及过程已经清楚了,怎么修复该漏洞?
if (Expression.VARTYPE !=  VT_BSTR)
{
If (VT_DISPATCH == Expression.VARTYPE )
Expression.pdispVal->AddRef();
*Result = Expression;
goto exit;
}
else
{
//执行表达式的计算
...........................
//将运算结果送入*Result
*Result = xxxxxxxxxx;
    }
虽然这漏洞已经出来大半个月了,但还有些同学没有弄明白,所以就发出来了,有错误的地方请大家指出

  • 标 题:答复
  • 作 者:llydd
  • 时 间:2009-07-31 18:33

格式太乱了,上传个DOC

上传的附件 Microsoft Office Web Components. CVE-2009-1136 分析.doc.zip