原帖地址:http://whereisk0shl.top/cve-2017-7269-iis6-interesting-exploit.html
感谢仙果和Wingdbg,我将我博客的这篇分析转到看雪和大家一起分享。我的博客会长期更新二进制分析的文章,希望能与大家多多交流,水平不高,大家多多包涵,多多指点。
0x00 前言
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。
我是一只菜鸟,如有不当之处,还望大家多多指正,感谢阅读!
0x01 弹弹弹--一言不合就“弹”计算器
1、 漏洞环境搭建
漏洞环境的搭建非常简单,我的环境是windows server 2003 r2 32位英文企业版,安装之后需要进入系统配置一下iis6.0,首先在登陆windows之后,选择配置服务器,安装iis6.0服务,之后进入iis6.0管理器,在管理器中,有一个windows扩展,在扩展中有一个webdav选项,默认是禁用状态,在左侧选择allow,开启webdav,之后再iis管理器中默认网页中创建一个虚拟目录(其实这一步无所谓),随后选择run->services.msc->WebClient服务,将其开启,这样完成了我的配置。
2、 触发漏洞
漏洞触发非常简单,直接在本地执行python exp.py即可,这里为了观察过程,我修改了exp,将其改成远程,我们通过wireshark抓包,可以看到和目标机的交互行为。
可以看到,攻击主机向目标机发送了一个PROPFIND数据包,这个是负责webdav处理的一个指令,其中包含了我们的攻击数据,一个<>包含了两个超长的httpurl请求,其中在两个http url中间还有一个lock token的指令内容。
随后我们可以看到,在靶机执行了calc,其进程创建在w2wp进程下,用户组是NETWORK SERVICE。
我在最开始的时候以为这个calc是由于SW_HIDE的参数设置导致在后台运行,后来发现其实是由于webdav服务进程本身就是无窗口的,导致calc即使定义了SW_SHOWNORMAL,也只是在后台启动了。
事实上,这个漏洞及时没有后面的<>中的http url,单靠一个IF:<>也能够触发,而之所以加入了第二个<>以及lock token,是因为作者想利用第一次和第二次http请求来完成一次精妙的利用,最后在<lock token>指令下完成最后一击。
我尝试去掉第二次<>以及<lock token>请求,同样能引发iis服务的crash。
0x02 CVE-2017-7269漏洞分析
这个漏洞的成因是在WebDav服务动态链接库的httpext.dll的ScStorageFromUrl函数中,这里为了方便,我们直接来跟踪分析该函数,在下一小节内容,我将和大家来看看整个精妙利用的过程。我将先动态分析整个过程,然后贴出这个存在漏洞函数的伪代码。
在ScStorageFromUrl函数中,首先会调用ScStripAndCheckHttpPrefix函数,这个函数主要是获取头部信息进行检查以及对host name进行检查。
在check完http头部和hostname之后,会调用wlen函数获取当前http url长度。
在利用的关键一次,我们获取的是poc中http://localhost/bbbbb的字符串,这个字符串长度很长,可以看到eax寄存器存放的是url长度,长度是0x2fd,随后会进入一系列的判断,主要是检查url中一些特殊字符,比如0x2f。
经过一系列的检查之后,会进入一系列的memcpy函数,主要就是用来构造虚拟文件路径,这个地方拷贝的长度没有进行控制,而拷贝的目标地址,是在外层函数调用stackbuff申请的地址,这个地址会保存在栈里。在ScStorageFromUrl函数中用到,也就是在memcpy函数中用到,作为目的拷贝的地址。
ScStorageFromUrl函数中实际上在整个漏洞触发过程中会调用很多次,我们跟踪的这一次,是在漏洞利用中的一个关键环节之一。首先我们来看一下第一次有效的memcpy
这次memcpy拷贝过程中,会将esi寄存器中的值拷贝到edi寄存器中,可以看到edi寄存器的值是0x680312c0,这个值很有意思,在之前我提到过,这个buffer的值会在外层函数中申请,并存放在栈中,因此正常情况应该是向一个栈地址拷贝,而这次为什么会向一个堆地址拷贝呢?
这是个悬念,也是我觉得这个利用巧妙的地方,下面我们先进入后面的分析,在memcpy中,也就是rep movs中ecx的值决定了memcpy的长度,第一次拷贝的长度是0x9。
接下来,回进入第二次拷贝,这次拷贝的长度就比较长了。
可以看到,这次拷贝的长度是0x17f,长度非常大,而在整个分析的过程中,并没有对拷贝的长度进行控制,因此,可以拷贝任意超长的字符串,进入这个堆空间。
这个堆空间非常有意思,存放的是一个vftable,这个vftable会在ScStorageFromUrl函数中的某个内层函数调用调用到,还记得之前分析的ScStripAndCheckHttpPrefi函数吗
获取完虚表之后,会获取到对应的虚函数,在ScStripAndCheckHttpPrefix函数中call调用到。但是由于之前的memcpy覆盖,导致这个vftable被覆盖
这个漏洞的原理非常简单,在PROPFIND中,由于对http的长度没有进行检查,导致在memcpy中,可以拷贝超长的字符串,覆盖到栈中的关键位置,下面来看一下伪代码。
0x03 CVE-2017-7269 Exploit!精妙的漏洞利用
其实通过上面的分析,我们发现这个漏洞的 原理非常简单,但是究竟如何利用呢,我们来看一下关于ScStorageFromUrl函数中,包含了GS check,也就是说,我们在进行常规的覆盖ret方式利用的情况下,将会把cookie也会覆盖,导致利用失败。
漏洞利用非常精妙,也就是用这种方法,巧妙的绕过了gs的检查,最后达到漏洞利用,稳定的代码执行,首先,WebDav对数据包的处理逻辑是在DAVxxx函数中完成的。比如当前数据包是PROPFIND,那么当前的函数处理逻辑就是DAVpropfind函数。
在内层的函数处理逻辑中,有一处关键的函数处理逻辑HrCheckIfHeader,主要负责DAVPropFind函数对头部的check,这个函数处理逻辑中有一处while循环,我已经把这个循环的关键位置的注释写在伪代码中。
如果看的比较迷糊,可以看我下面的描述,首先这个while函数中,有一个非常有意思的函数PszNextToken,这个函数会连续获取<>中的http url,直到后面没有http url,则跳出循环,这也是这个漏洞利用的关键条件。
首先,第一次会处理IF后面的第一个http url,这个url就是http://localhost/aaaa..,这个处理过程,实际上就完成了第一次溢出,首先stackbuffer会通过CStackBuffer函数获取,获取到之后,这个值会存放在stack中的一个位置。接下来会进行第一次ScStorageFromUrl,这个地方会对第一个<>中的http url处理。长度是0xa7。
这个a7长度很小,不会覆盖到gs,因此可以通过security check,但是这个a7却是一个溢出,它超过了stack buffer的长度,会覆盖到stack中关于stack buffer指针的存放位置。这个位置保存在ebp-328的位置。
可以看到,第一次ScStoragePathFromUrl的时候,拷贝的地址是一个栈地址,通过stackbuffer申请到的,但是由于memcpy引发的栈溢出,导致这个地方值会被覆盖。
经过这次stack buffer overflow,这个值已经被覆盖,覆盖成了一个堆地址0x680312c0。接下来进入第二次调用。
第二次获得http://localhost/bbbbb...的长度,这个长度有0x30d,非常长,但是对应保存的位置变了。
可以看到,第二次利用的时候,会把ebp-328这个地方的值推入栈中,这个地方应该是stack buffer的地址,应该是个栈地址,但是现在变成了堆地址,就是由于第一次栈溢出,覆盖了这个变量。
而这个值,会作为参数传入ScStorageFromUrl函数,作为memcpy拷贝的值。
这也就解释了为什么我们在上面分析漏洞的时候,会是向堆地址拷贝,而这一次拷贝,就不需要控制长度了,因为这个地方的值已经是堆地址,再怎么覆盖,也不会覆盖到cookie。这里未来要覆盖IEcb虚表结构。从而达到漏洞利用。这样,第二次向堆地址拷贝之后,这个堆地址会覆盖到IEcb的虚表,这个虚表结构会在最后利用时引用到。
在PoC中,有一处<locktoken>,这个会触发漏洞利用,是在CheckIfHeader之后到达位置,在CheckIfHeader的PszToken函数判断没有<>的http url之后,break掉,之后进入lock token处理。
这时候对应的IEcb已经被覆盖,这样,在进入ScStoragePathFromUrl函数之后,会进入我们在漏洞分析部分提到的CheckPrefixUrl函数,这个函数中有大量的IEcb虚表虚函数引用。
和大家分享了这个精妙利用,一般可能都会觉得是第二次url bbbbb的这个memcpy覆盖了关键函数导致的溢出、利用,实际上,在第一次url aaaaaa中,就已经引发了栈溢出,覆盖到了stackbuffer申请的指向栈buffer的指针,这个指针存放在栈里,用于后续调用存放虚拟路径,由于第一次栈溢出,覆盖到了这个变量导致第二次url bbbbb拷贝的时候,是向一个堆地址拷贝,这个堆地址后面的偏移中,存放着IEcb的vftable,通过覆盖虚表虚函数,在最后locktoken触发的ScStoragePathFromUrl中利用虚函数达到代码执行。
而这个过程,也是巧妙的绕过了GS的检查
0x04 简析ROP及shellcode
这个漏洞使用了一些非常有意思的手法,一个是TK教主在13年安全会议上提到的shareduserdata,在ROP中,另一个是alpha shellcode。
首先,在前面虚函数执行之后,会先进行stack pivot,随后进入rop。
经过一系列ROP之后,会进入KiFastSystemCall,这是利用SharedUserData bypass DEP的一环。
[招生]科锐逆向工程师培训(2024年11月15日实地,远程教学同时开班, 第51期)