漏洞编号 CVE-2012-1889
1.软件简介
Microsoft XML Core Services (MSXML)是一组服务,可用JScript、VBScript、Microsoft开发工具编写的应用构建基于XML的Windows-native应用。
2.漏洞成因
Microsoft XML Core Services 3.0、4.0、5.0和6.0版本中存在漏洞,该漏洞源于访问未初始化内存位置。远程攻击者可利用该漏洞借助特制的web站点,执行任意代码或导致拒绝服务(内存破坏)。
该漏洞产生于msxml3.dll模块中,msxml3.dll是微软的一个SAX2 帮助程序类。主要用途包括:XSL 转换 (XSLT) 和 XML 路径语言 (XPath) 的完全实现、对 XML (SAX2) 实现的简单 API 的修改,包括与万维网联合会 (W3C) 标准和 OASIS 测试套件保持更高一致性。
3.验证漏洞
WinXP+IE6.0环境
POC1
CSLID:clsid:f6D90f11-9c73-11d3-b32e-00C04f990bb4(MSXML3.dll中使用到的ID)
1.1
使用IE加载POC1引发漏洞,IE6.0崩溃
1.2
1.3
在Windows XP+IE6的环境下用Windbg附加调试POC1,程序运行并崩溃,触发异常的地方在5dd8d772
1.4
向上观察[ecx+18h],发现此处内存未初始化,所以程序崩溃原因是call了一个没有数据的地址[ecx+18h]
1.5
漏洞触发时的执行环境
其中导致崩溃的[ecx+18h]中的ecx来源于eax,而eax来自[ebp-14h],故此处可以推出问题产生的位置应为mov eax,dword ptr[ebp-14h]
1.6
1.7
POC2
1.8
由POC1可知,其问题是call了一个没有数据的地址[ecx+18h],而我们也分析了问题产生的位置应为mov eax,dword ptr[ebp-14h],既然如此,我们就进行填充栈的操作,让[ebp-14h]的地址中包含数据,此时暂不考虑正确性,验证需求即可。
使用IE加载POC2引发漏洞,IE6.0崩溃
1.9
继续使用Windbg附加调试POC2,程序运行并崩溃,此时触发异常的地方变为了5dd8d75d
1.10
观察堆栈情况,可以看到栈已经被填充成0C0C0C0C,但得到的结果并不是我们想要的
1.11
使用OD简单调试分析
1.12
程序在mov ecx,dword ptr ds:[eax]指令处断下,其中eax的值来源于栈,此时栈中的数据为0x0C0C0C0C,该指令需要从0x0C0C0C0C中取内容,所以引发了数据访问权限异常。
使用IDA导入msxml3.dll的符号后查看异常处属于的函数
1.13
F5可以看到伪代码显示内容,其中+0x18的内容即程序崩溃时的eip
1.14
上方还有一个+8的内容,对比POC中的内容,且结合其反汇编代码call猜测其为definition的调用。
1.15
1.16
在IDA中搜索definition,显示为get_definition,简单分析
1.17
此处仅在v5内容存在的情况下对a2进行处理,而v5取决于definition的ret的值,如果v5不存在,ret后调用a2指向的内容,但有可能a2未进行初始化的操作,正常情况来说应在else中设置*a2 = 0,由此可以判断,此处崩溃的原因是由于栈内未初始化数据导致的。
接下来,使用0x0c0c这个滑板指令进行溢出攻击,想办法将[ebp-0x14]处的内容填充成0x0c0c0c0c
POC3
1.18
sxe ld:xxx,在加载XX模块时下断点
1.19
查看0x0c0c0c0c处的内容
1.20
接着就可以安排shellcode 精心构造一个块,然后把内存中200M空间喷射为这个块。并让0c0c0c0c + 18 = 0c0c0c24 这个位置变成滑板指令,最终滑向精心构造的代码。
POC4
喷射堆块由以下两部分数据组成内存块:
1、nops (许多nop指令)(一般写入200M的0c0c)
2、shellcode(放置在喷射块的尾部)
1.21
通过写入大量的0C0C,使得eax的值变为0C0C0C0C。由于POC已经写入了200M的0C0C,所以mov ecx,[eax]指令执行后ecx的值也为0C0C0C0C。call dword ptr [ecx+18h]中地址ecx+18h(0C0C0C24)处的数据也为0C0C0C0C。此时call跳转到0C0C0C0C处执行,此时ShellCode位于0C0C0C0C之后即堆喷射块尾部即可执行。
此外,由于堆内存之间穿插着系统内存,如果一次性填充200M的内存区域,可能会导致其执行到系统内存,为了避免这种情况,我们将200M堆内存分块分成200个1M大小的堆,每个堆块都是由滑板指令(0C0C0C0C)和ShellCode组成,这样无论如何ShellCode都会被执行。
1.22
WinXP+IE8.0环境
2.1
DEP 的主要作用是阻止数据页(如默认的堆页、各种堆栈页以及内存池页)执行代码。微软从Windows XP SP2开始提供这种技术支持,根据实现的机制不同可分为:软件DEP(Software DEP)和硬件DEP(Hardware-enforced DEP)。
由于IE8.0加入了DEP保护,对Heap Spray做了些许限制,采用直接字符串赋值的方式会被禁止,因此需要将堆喷射时的代码进行简单修改。
修改前:
2.2
修改后:
2.3
(与IE6.0环境中POC3方法一致)
POC1
2.4
测试,发现触发了DEP保护
2.5
2.6
windbg附加后的0x0c0c0c0c处的内存属性
2.7
ByPass DEP
采用代码重用的方法实现对DEP的绕过,包括利用系统API改写内存页属性、调用执行系统命令或加载可执行文件引入外部代码、改写安全配置(IE浏览器的SafeMode
标志位等),其中利用系统统API改写内存页属性是最为常见的利用方法。
指令!heap -p -a 0c0c0c0c获取调用栈,查看0c0c0c0c地址处的UserPte地址
相对偏移 = (目标地址 - UserPtr地址)% 0x1000 / 2
2.8
2.9
指令!py mona mod,查看当前系统模块信息,XP系统中大多数dll都没有开启ASLR
2.10
调试用到的异常地址
代码偏移 = 原异常地址 - 原基址 + 新基址
5dd8d772 - 5dd50000 + 038b0000
2.11
2.12
设置断点,观察运行结果
2.13
根据公式,计算相对偏移
相对偏移 = (0C0C0C0C - 0C060020)% 0x4000 / 2 = 0x5F6
获取一个相对偏移,就可以构建多个相同的内存块,以Padding,Ret2Libc,Payload,FillData四部分构成,以此构成精准堆喷射。
2.14
一个内存块大小为0x4000,并且有0x02的块起始与0x21的块结尾,故构造数据块时要预留出此部分的大小。
块大小的计算:cBlock = cBlock.substring(2,nBlockSize-0x21)
POC2
2.15
2.16
0C0C0C0C处被修改为了我们自己写入的数据
2.17
Ret2libc绕过思路
由于DEP保护了Shellcode所属内存页的执行属性,使的Shellcode无执行权限,此时如果希望任然执行Shellcode,就需要发生异常的代码拥有执行权限,故考虑将其地址改变为系统代码内存地址。而系统代码地址并非为连续代码,需要使用esp + ret指令将其连接起来完成,只要将拼接后的指令组合为调用VirtualProtect,即可实现更改内存属性为可执行。
2.18
指令!py mona findwild -s "xchg eax,esp#ret" -m msvcrt.dll寻找XCHG eax,esp + ret 的地址(由于windwos程序运行时要依赖微软运行时库,所以在msvcrt.dll中找)
2.19
我们需要的数据在精准堆喷射时存放在0x0C0C0C0C,eax的值也是0x0C0C0C0C,进行XCHG eax,esp后,esp的值为0X0C0C0C0C,当执行ret指令时,就会将0X0C0C0C0C作为指令地址了
2.20
已知,eax的值来源自栈[ebp-0x14],此时[ebp-0x14]为0C0C0C0C
2.21
eax为0C0C0C0C,ecx也为0C0C0C0C,触发漏洞call的位置就为(0C0C0C0C+18)=0C0C0C24
为了保证数据都是0C0C0C0C,[eax-4]也需要是0C0C0C0C,即eax的值变成0C0C0C08,这也才能保证 0C0C0C24在ret后,esp就会自动变成0C0C0C0C。
POC3
cRet2Libc :"\u0000\u0000" -> "\u0C0C\u0C0C" SrcImgPath :"\u0C0C\u0C0C" -> "\u0C0C\u0C08"
2.22
此时根据精准堆喷射反馈的结果可以看出,我们只需要将66666666更改为xchg eax,esp + ret地址就可以了
这里使用的地址为0x77be5ed5
cRet2Libc :"\u6666\u6666" -> "\u5ED5\u77BE"
2.23
运行至xchg eax,esp之后,esp= 0x0C0C0C08,此时0x0C0C0C08中的内容为0x0C0C0C0C,导致ret指令后将0x0C0C0C0C作为指令地址去执行里面的内容,这样明显是不符合我们的想法的,需要寻找别的利用代码。
向下寻找,正好附近就有一个call,可以看到其eax来自于esi,esi又来自与eax
2.24
call上方的push esi会让esp - 4,[esp] = 0x0C0C0C08
如果0C0C0C24是ret就不影响其他寄存器,这也[eax+8]为0C0C0C14,将此处更改为xchg eax,esp + ret地址
cRet2Libc :"\u5ED5\u77BE" -> "\u5ED6\u77BE" "\u2222\u2222" -> "\u5ED5\u77BE"
2.25
2.26
执行后esp = 0x0c0c0c10,如果这里也是ret,那么esp = 0x0c0c0c14,xchg eax,esp又要被执行,所以此处使用pop xxx指令来将0x0c0c0c14里 ret的内容作为数据放到某一寄存器中。
指令!py mona findwild -s "pop ebp#ret" -m msvcrt.dll,寻找pop ebp+ret指令
2.27
最后拟定VirtualProtect调用前的RET指令就行了。
2.28
指令u virtualprotect寻找VirtualProtect地址
2.29
使用msvcrt.dll中具有写入权限的.data区段
2.30
指令!address msvcrt.dll+VOffset获取信息
2.31
原始内存属性的保存地址(使用最后几个字节):77c31000 + 00003000 - 4 = 77c33ffc
2.32
Ret2libc结构
2.33
修改代码后进行测试
2.34
成功利用
2.35