[分享]Microsoft Office Web Components. CVE-2009-1136 分析
发表于:
2009-7-31 18:24
8977
[分享]Microsoft Office Web Components. CVE-2009-1136 分析
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 (I蓔P?打葇...
其中偏移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;
}
虽然这漏洞已经出来大半个月了,但还有些同学没有弄明白,所以就发出来了,有错误的地方请大家指出
[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课