实验环境 操作系统:Vmware Windows xp sp1 Office版本:Microsoft OfficeWord 2003 (11.5604.5606) 漏洞文件:MSCOMCTL.OCX 6.1.95.45 调试器:Windbg:6.12.0002.633 x86 前言Cve-2012-0158漏洞出现在MSCOMCTL.OCX,是微软Office系列办公软件在处理MSCOMCTL的ListView控件的时候由于检查失误,导致攻击者可以通过构造恶意的文档执行任意代码,本文我将分析这个漏洞的起因。 漏洞验证我从网上拿到了一个样本:May.doc 。首先我在虚拟机用Word 打开这个样本, 计算器弹出来,接着Word就退出了。但我再一次打开这个样本,发现程序报错。 所以在测试前记得先备份,因为下面我们多次要打开这个样本。
漏洞分析
打开样本弹出计算器,所以Shellcode应该会调用WinExec函数。用Windbg附加WINWORD.EXE,bp kernel32!WinExec下断 然后运行,把样本拖进word。 程序断在WinExec入口点。这时候计算器还没弹出来,看一下WinExec的参数,发现是a.exe。我去到这个目录,发现它就是一个计算器。 看来shellcode应该是把系统的calc复制到别的目录再运行它。 WinExec的返回地址是0x00127dee。往上翻翻,没发现shellcode从哪开始。Kb打印堆栈 [Asm] 纯文本查看 复制代码 1 2 3 4 5 6 | ChildEBP RetAddr Args to Child
00127b20 00127dee 0cd5a5b0 00000000 0cd5a5b0 kernel32!WinExec
WARNING: Frame IP not in any known module. Following frames may be wrong.
00127b50 275c8a0a 07d8c008 0cd3b868 0001c000 0x127dee
00127b8c 00127c05 1005c48b c7000001 4d032400 MSCOMCTL!DllGetClassObject+0x41cc6
00000000 00000000 00000000 00000000 00000000 0x127c05
|
重新载入,在MSCOMCTL!DllGetClassObject+0x41cc0下断 断在这个call的上一句,Kb发现这一层函数的返回地址是0x 275e701a,它前面一个call是 接着两次p,然后搜索内存 在0x00127b88发现万能跳转0x7ffa4512和shellcode。并且在 275c8a05 e863fdffff call MSCOMCTL!DllGetClassObject+0x41a29 (275c876d) 之前这里还没有被shellcode覆盖,显然,溢出发生在 275c8a05 e863fdffff call MSCOMCTL!DllGetClassObject+0x41a29 (275c876d)
接着上面,我单步执行 是不是很熟悉呢?,到这里我们知道的信息如下:
[Asm] 纯文本查看 复制代码 1 2 | Shellcode执行发生在下面 call 返回
275e7015 e8ad19feff call MSCOMCTL!DllGetClassObject+0x41c83 (275c89c7)
|
溢出发生在 [Asm] 纯文本查看 复制代码 1 | 275c8a05 e863fdffff call MSCOMCTL!DllGetClassObject+0x41a29 (275c876d)
|
Shellcode通过jmp esp覆盖返回地址利用。顺便dump部分shellcode [Asm] 纯文本查看 复制代码 1 2 3 4 5 6 7 8 | 00127b88 12 45 fa 7f 90 90 90 90 90 90 90 90 8b c4 05 10 .E..............
00127b98 01 00 00 c7 00 24 03 4d 08 e9 5a 00 00 00 6b 65 .....$.M..Z...ke
00127ba8 72 6e 65 6c 33 32 00 df 2d 89 8c 1b 81 7d ef 42 rnel32..-....}.B
00127bb8 9d 85 85 d6 4e 99 59 5a 61 d8 54 93 77 77 21 9d ....N.YZa.T.ww!.
00127bc8 4a 62 68 c3 53 a3 83 6a 6b df 5c 5a 8a 1d 2b 4f Jbh.S..jk.\Z..+O
00127bd8 2c 45 28 81 71 f5 40 01 92 8f 05 ba 36 c1 0a 61 ,E([email].q.@.....6..a[/email]
00127be8 61 61 61 73 68 65 6c 6c 33 32 00 8b 98 8a 31 61 aaashell32....1a
00127bf8 61 61 61 6f 70 65 6e 00 e8 11 02 00 00 6a ff e8
|
接下来用IDA打开有漏洞的MSCOMCTL.OCX 。定位前面那两个关键call ,先看上层call [Asm] 纯文本查看 复制代码 1 | 275e7015 e8ad19feff call MSCOMCTL!DllGetClassObject+0x41c83 (275c89c7)
|
首先分配20个字节空间 275C89CA sub esp, 14h ; 分配20字节局部空间 然后调用 275C89DA call sub_275C876D 这个call的功能是从样本读取dwBytes,这里是0CH字节返回到eax指向的内存地址。读取到的0xC字节如下所示: 00127b70 43 6f 62 6a 64 00 00 00 82 82 0000
接着程序 如果读取的12字节开始4个字节不等于0x6A626F43h,则跳转到错误处理。否则执行 275C89F3 cmp [ebp+dwBytes], 8
我这里[ebp+dwBytes]为0x00008282。显然0x00008282大于8,所以程序转到 这里其实就是我们真正溢出的那个CALL。我们看这个call有三个参数 Sub_275C876D(8字节局部空间起始地址,lpMem,字节数) 【这里字节数是0x8282】
跟进这个call看看,首先 [Asm] 纯文本查看 复制代码 1 2 3 4 | 275c877f 6a04 push 4
275c8781 51 push ecx
275c8782 53 push ebx
275c8783 ff500c call dword ptr [ eax +0Ch] ole32!CExposedStream::Read (771ca84b)}
|
读取4个字节到[ebp+var_4],返回值和0比较,如果小于0,读取失败,转到失败处理,否则 [ebp+var_4]和[ebp+dwBytes]即0x00008282比较,相等则 分配0x8282字节空间,接着 读取0x8282字节到刚才分配的堆空间。接着 我们看到:275C87CB rep movsd 再看看此时的 EDI=0x127b7c ESI=0x20e008 (指向前面读取的0x8282字节) ECX=0x20a0 而我们看 275e7015 e8ad19feff call MSCOMCTL!DllGetClassObject+0x41c83 (275c89c7)
函数的返回地址是0x127b88 所以这里的意思就是把从样本中读取的的0x8282字节复制0x8280到0x127b7c,上层函数的返回地址是0x275c8a0a。上上层函数的返回地址在0x127b88。所以 275C87CB rep movsd 所在的这层函数在溢出后能正常返回,上上层函数在返回时候才跳去执行shellcode。分析完成,我们看到其实漏洞发生的根本原因在于 .text:275C89F3 cmp [ebp+dwBytes], 8 .text:275C89F7 jb loc_275D 比较从样本读取过来的数据时候处理错误,这里应该是大于8个字节就转到错误处理,它写成了小于8个字节转到错误处理。而这个字节大小是我们可以控制的,所以当我们传递比如0x8282,那么程序就会读取样本相应位置可控的0x8282字节到堆空间,然后把这0x8282字节复制到0x127b7c地址,进而覆盖掉函数的返回地址,执行我们的shellcode。漏洞原因伪代码表示如下: [C] 纯文本查看 复制代码 01 02 03 04 05 06 07 08 09 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 | #include <stdio.h>
int main()
{
sub_275C89C7();
return 0;
}
sub_275C89C7()
{
char temp[20];
Read(temp,12);
if (temp[0]==0x6A626F43)
{
if (temp[8]<8)
{
exit (-1);
}
else
{
sub_275C876D(temp[8],lpMen,*temp[8])
}
}
else
{
exit (-1);
}
}
sub_275C876D(temp[8],lpMen,*temp[8])
{
char val[4];
if ( Read(val,4) <0 );
{
exit (-1);
}
else
{
if (val[0]==*temp[8])
{
if ( buffer=HeapAlloc(hHeap,0,val[0]) );
{
Read(buffer,val[0]);
memcpy (temp[12],buffer,0x8280);
}
else
{
exit (-1);
}
}
else
{
exit (-1);
}
}
}
|
漏洞利用
此样本是通过用jmp esp地址覆盖函数返回地址进行利用的 搜一下样本,发现shellcode 之前看了通过利用异常处理进行的利用。就像试试这个漏洞能不能用此方法。在溢出即将发生时候 [Asm] 纯文本查看 复制代码 1 | 275c87cb f3a5 rep movs dword ptr es :[ edi ], dword ptr [ esi ]
|
我dump异常链 [Asm] 纯文本查看 复制代码 1 2 3 4 5 6 7 8 9 | 0:000> !exchain
0012f908: USER32!_except_handler3+0 (77d3edbf)
CRT scope 0, func: USER32!UserCallWinProcCheckWow+155 (77d4f0cc)
0012f968: USER32!_except_handler3+0 (77d3edbf)
0012ffb0: WINWORD!wdGetApplicationObject+27bcf8 (30a6c5d4)
0012ffe0: kernel32!_except_handler3+0 (77e74809)
CRT scope 0, filter: kernel32!BaseProcessStart+29 (77e737c8)
func: kernel32!BaseProcessStart+3a (77e80128)
Invalid exception stack at ffffffff
|
此时EDI=0x00127b7c,复制33408字节,最多覆盖到0x12FDFC,而最近的异常处理在0x12f908。 理论上可以利用。但是当我修改样本shellcode后面的数据时shellocde后面的数据在复制前已经不是原始的的了。所以尽管可以覆盖到SEH,但数据似乎是不可控的。
总结
这是我分析的第一个CVE,以前都是看书,写写demo调试,基本就是strcpy那种简单的溢出。看别人分析的CVE又看不懂。自己分析完一个,感觉收获蛮大,了解到其实CVE漏洞也没有那么神秘高深,还有漏洞居然还能这样产生,而不是strcpy,memcpy造成溢出,居然是一个小小的判断失误就导致了漏洞,真是千里之堤溃于蚁穴。这个漏洞是由于MSCOMCTL.OCX在解析数据的时候将需要处理的数据拷贝到栈上,而对需要拷贝的数大小未做正确检查,造成标准的栈溢出漏洞。
|