在NSA泄露的文件中,我们注意到在windows/exploits目录中Explodingcan漏洞利用文件。分析了下该Explodingcan工具是对Windows2003 IIS 6.0 上的WEBDAV漏洞(CVE-2017-7269)的利用。


NSA泄露的工具包Explodingcan工具路径:

640?wxfrom=5&wx_lazy=1

这次NSA泄露的工具包中Explodingcan-2.0.2.fb文件内容:

640?wxfrom=5&wx_lazy=1

漏洞环境:

操作系统:Windows Server 2003 R2 x86 EN Enterprise

IIS服务器:IIS6.0版本,开启WebDav扩展,如下图:

640?wxfrom=5&wx_lazy=1

启动WebClient服务,如下图:

640?wxfrom=5&wx_lazy=1

信息备注:

WebDAV

一种基于HTTP 1.1协议的通信协议。它扩展了HTTP1.1,在GET、POST、HEAD等几个HTTP标准方法以外添加了一些新的方法,使应用程序可对WebServer直接读写,并支持写文件锁定(Locking)及解锁(Unlock),还可以支持文件的版本控制。

PROFIND

方法用于返回指定目录的内容。当IIS开放了目录浏览权限,就会返回站点指定的目录下文件列表。

漏洞分析:

640?wxfrom=5&wx_lazy=1

从漏洞提交报告来看为ScStoragePathFromUrl函数出现了问题。使用WinDBG附加到进程w3wp.exe。有时会附加不到w3wp.exe进程上,因为附加进程列表中看不到w3wp.exe,根据网上不完全可靠描述是20分钟没新的请求到达服务器或者其他一定条件满足时等,系统会回收w3wp.exe进程。因此为了附加调试程序,需要每次都新发起一个请求。

640?wxfrom=5&wx_lazy=1

此时输入u ScStoragePathFromUrl,此时开始从符号服务器下载对应符号了。可以得知该函数位于httpext.dll。其对应路径位于c:\windows\system32\inetsrv\httpext.dll。(输入lmf得知)。

640?wxfrom=5&wx_lazy=1

现在开始动态调试漏洞程序。根据前面得知对httpext!ScStoragePathFromUrl添加断点,WinDBG的命令行内输入bp httpext! ScStoragePathFromUrl,接着在宿主机运行python POC文件名 IP,发现确实触发了断点。下面开始使用IDA f5的反编译的代码和WinDBG调试的指令、信息等一起进行对照。

640?wxfrom=5&wx_lazy=1

WinDBG输入kb查看栈回溯。

640?wxfrom=5&wx_lazy=1

通过漏洞公告了解到该POC应该使用的是WEBDAV中的propfind方法,其对应到调用栈中的DAVPropFind。进入Execute成员函数内,发现很长,分析该HrCheckStateHeader函数后,发现其直接调用了HrCheckIfHeader,进而去分析HrCheckIfHeader函数。

640?wxfrom=5&wx_lazy=1

在这里先做一件事,就是在进入到HrCheckIfHeader中输入命令bp MultiByteToWideChar(因为PoC中可以看到ShellCode均被编码过了猜测程序存在编码转换的过程)。其函数原型可参考如下:

640?wxfrom=5&wx_lazy=1

函数入口处输入dd esp,查看lpMultiByteStr和lpWideCharStr两个参数。分别对应划红线的位置。

640?wxfrom=5&wx_lazy=1

此时还没进行转换,输入pt执行到该函数返回处再查看对应的内容。

640?wxfrom=5&wx_lazy=1

在这之后,所展示的内容都为截图右下方的unicode版本的字符串内容。

继续观察HrCheckIfHeader的while(2)循环外的IFITER::PszNextToken((int)&v20,0);发现其返回的地址是如下If:后起始的内容。

640?wxfrom=5&wx_lazy=1

640?wxfrom=5&wx_lazy=1

定位到上图所展示的<后,使用wcslen计算整个http起始的长度。调用第一处ScStoragePathFromUrl,POC的内容导致其返回1(代表其需要变换缓冲区大小)。

640?wxfrom=5&wx_lazy=1

那么接着就会继续调用一次ScStoragePathFromUrl。此时该函数返回0。for ( i= IFITER::PszNextToken((int)&v20, 2); ; i =IFITER::PszNextToken((int)&v20, v19) )循环中由于首个字符即为0x3c(<的ascii码)。HrValidTokenExpression(a1,v17, v8, 0);返回非0以及v22变量也即为0x30转入label_27,就会进入v26=1,v19=3后进入下一个循环。导致循环里的IFITER::PszNextToken返回空。

640?wxfrom=5&wx_lazy=1

640?wxfrom=5&wx_lazy=1

所以进入上图函数处理中,可以看到第2个参数的变化,该函数的具体逻辑不进行分析,只需知道这里会返回第2段<的内容。

640?wxfrom=5&wx_lazy=1

但是这里的ScStoragePathFromUrl的调用直接返回0,所以只会进入一次。我们外层对ScStoragePathFromUrl的调用分析到这里。

(1)分析完程序中对漏洞所在函数的调用框架后,需要对ScStoragePathFromUrl的第一个函数ScStripAndCheckHttpPrefix进行分析。

程序在ScStripAndCheckHttpPrefix(注意这些个函数的调用方式都是fastcall,前两个参数都是通过ecx,edx进行传递的)中会先调用。httpext!CEcbBaseImpl<IEcb>::CchUrlPrefixW返回unicode版本http://的字符数,再通过比较http://和POC中If:<为起始,>为结尾的字符串。

640?wxfrom=5&wx_lazy=1

接着如果满足一致性,接着调用httpext!CEcbBaseImpl<IEcb>::CchGetServerNameW,接着比较http://后的第一个字符开始的内容是否和http头里的host字段一致。如果一致则直接返回0,对于该内部函数先分析到这里。

640?wxfrom=5&wx_lazy=1

(2)回到ScStoragePathFromUrl,对应返回的str指针被修改为了host后的第一个/,也就会先判断这里是否和/的ascii码是一致的。接着立马得到url的unicode版本的字符数。根据之前分析第一次这里得到的是0x97。

640?wxfrom=5&wx_lazy=1

(3)通过IEcbBase::ScReqMapUrlToPathEx来完成虚拟目录到真实路径的变换。返回后参数变为如下。

640?wxfrom=5&wx_lazy=1

(4)当HrCheckIfHeader调用ScStoragePathFromUrl时,进入了一系列的qmemcpy的调用,也就是这里的ecx控制长度,esi指向该物理路径处,edi为某栈内位置。

640?wxfrom=5&wx_lazy=1

640?wxfrom=5&wx_lazy=1

这个函数的最后一处qmemcpy(&v35[v22],v29, 2 * v28 + 2);则会完成0x4c个双字的拷贝。通过IDA可知这里的拷贝地址是v35决定的,而v35来源于传递参数a3。需要观察这个a3是从何而来。其对应于HrCheckIfHeader中的str变量。其通过push  dword ptr[ebp-21ch]完成传递的。

640?wxfrom=5&wx_lazy=1

其对应的位置在0xfff90c(ebp-328h)的位置存放着0xfff804(ebp-430h),二者均是栈内地址空间的,根据IDA得到的局部变量v28只有104h(260)的长度。根据POC来看这次所有qmemcpy将会覆盖0xfff804到0xfff958位置处,对应的0xfff90c的位置将被覆盖成0x680312c0,这个开始覆盖的栈地址就是位于HrCheckIfHeader函数体内的,而该函数是具有GS(securitycookie)保护的,但是其存放方式和一般函数不一样通过EH_prolog等完成具体布置可见下图。

640?wxfrom=5&wx_lazy=1

在WinDBG中得到位于HrCheckIfHeader的时候,secuirty cookie的大体位置如下。

640?wxfrom=5&wx_lazy=1

而我们覆盖到最高的位置就是fff958h,所以此时不会对securitycookie产生影响避免了检查。

(5)之前阐述了第一个栈溢出时的情况,现在通过PszNextToken进入第二次循环的情况(位于同一个HrCheckIfHeader函数体内所以之前的覆盖的fff90ch处还是0x680312c0这个堆地址),也就是第2段<http://localhost/bbbbbbb起始的内容了。所以这里进入ScStoragePathFromUrl的qmemcpy将会对0x680312c0这个地址进行复制。但是这个地址到底是什么区域的内存地址呢?

尝试在WinDBG中输入!address该地址,发现这个区域是rsaenh.dll模块的地址。

640?wxfrom=5&wx_lazy=1

目前无法确定这0x680312c0如何使用,但是可以发现ScStoragePathFromUrl被调用了后面还多次被调用到所以对那里的情况进行继续分析。

(6)观察发现又有几次调用ScStoragePathFromUrl,以此为线索,发现其调用堆栈发现不来自于HrCheckIfHeader了。

640?wxfrom=5&wx_lazy=1

其调用来自于CPropFindRequest::Execute->FGetLockHandle->CParseLockTokenHeader ::hrGetLockIdForPath。我们知道ScStoragePathFromUrl存在栈溢出的问题我们看看此处发起的调用。调试时观察到拷贝源地址和目的地址如下。也就是0xfffab4和0xfff500处。

640?wxfrom=5&wx_lazy=1

0xfff500是一个栈地址,故我们需要确定是哪一层函数的局部变量,我们输入r ebp查看到为0xfff93c,所以不在当前函数范围内,接着我们继续在WinDBG中输入pt让其执行到ret并回到CParseLockTokenHeader::HrGetLockIdForPath处,同时r ebp发现为0xfffbd0,所以应该就在这个函数体内,继续退回上一层函数,发现不行因为这里已经发生了异常很有可能shellcode已经被执行了。

现在我们定位那些变量被覆盖了,0xfffbd0-0xfffab4=0x11c,查看ida反编译的代码中对应什么。

640?wxfrom=5&wx_lazy=1

现在IDA也清楚的标志了v29就是被覆盖的起始处。这段溢出的范围为0xfffab4-0xfffc07处,其实是跨越了CParseLockTokenHeader::HrGetLockIdForPath到FGetLockHandle2个函数体,也就是CParseLockTokenHeader::HrGetLockIdForPath的v29到v32全部被覆盖。

但是此时没有其他信息了,我决定看在哪里执行了shellcode并导致了进程的结束,其实就是一次次的f10和f11的尝试,一步步地我发现是在CParseLockTokenHeader::HrGetLockIdForPath返回前,再进一步定位了ScStoragePathFromUrl,接着是在我们之前分析过的ScStripAndCheckHttpPrefix函数体内。

640?wxfrom=5&wx_lazy=1

以0x680313c0作为对象地址,取出虚函数表,访问第9处索引的虚函数指针并访问执行。既然看了0x680313c0这个地址了,我们来了解下到底是哪个位置被覆盖成了这个值。

640?wxfrom=5&wx_lazy=1

我们在0xfffbd8和0xfffbe8处看到了这个值,还记得ebp在CParseLockTokenHeader::HrGetLockIdForPath时ebp为0xfffbd0吗?这两个地址覆盖的地址在ebp所指向地址的附近。0xfffbd8-0xfffbe7处都是函数传入参数,而0xfffbe8开始就是FGetLockHandle的V7变量,看下IDA的视图。

640?wxfrom=5&wx_lazy=1

观察到v7是一个CParseLockTokenHeader变量,传递了存放他的栈地址到内部函数。因为CMethUtil::ScStoragePathFromUrl(*(CMethUtil**)v11, v28, Str, &v26)中v11存放的是前面提到的栈地址,又以该地址间接寻址,那么0x680313c0就被作为参数传入了,也就是之前覆盖的堆地址范围内,回到之前的分析,最终ScStripAndCheckHttpPrefix调用其前面描述的虚函数时引发了Shellcode的执行,如下图:

640?wxfrom=5&wx_lazy=1

斗象科技能力中心 -  TCC Group


扫码关注漏洞盒子↓↓

640?wx_fmt=png&wxfrom=5&wx_lazy=1

本文版权归漏洞盒子所有