CVE-2017-7269:IIS6.0远程代码执行漏洞逆向分析记录

前言以及准备工作

  关于CVE-2017-7269,网上的大神们的分析的都很有高度,我做为一个小白,拿着这种给了exp的漏洞,我没有想太多,直接windbg和IDA动静结合,调试进行分析,不过,这样可能造成分析有些片面,毕竟是纯逆向进行分析。
好了,首先说说。
环境Windows Server 2003R2 上开启WebDAV服务的IIS6.0。
工具 Windbg,IDA
接着修改网上的EXP代码我们现在不需要完美执行这个EXP,我们需要它崩溃,所以去掉作者添加的shellcode,只保留EXP部分,如下图。

现在进行调试,Windbg直接附加进程,上网查了查IIS的进程名称为W3WP.EXE,好的附加,然后g跑起来。接着把修改后的EXP运行。好了,如下图所示,它崩溃了。

退出Windbg,再次附加W3WP.EXE的时候,发现已经找不到这个进程了,这是IIS应用池机制的问题,解决办法有两个
[1]备份虚拟机镜像
[2]在IIS信息服务管理器里面把网站下的任意网站右键浏览一下,W3WP.EXE就会重新启动

接着进行调试,这个漏洞作者是给了漏洞函数的ScStoragePathFromUrl。使用IDA静态分析吧,那么dll是那个呢,作者并没写明dll名称,但是在IIS信息服务管理器开启WebDAV服务的时候可以知道,如下图。Dll名称为httpext.dll

我们直接把这个dll,放入IDA,加载其符号表。然后CTRL+P 查找函数,输入函数名ScStoragePathFromUrl。双击进行进行分析。

其中很有趣地方里面有明显的内存拷贝,而且有if判断,那么是简单的栈溢出吗?

If判断地址

漏洞原理攻击分析

我们继续进行动态调试,知道了dll的名称,我们先用sxe ld:httpext下加载dll时的断点。
通过IDA知道了ScStoragePathFromUrl这个函数的retn地址为0x673F702C,那我们直接在这个地址下断点,看看每次执行完这个函数时候栈返回地址是否被覆盖,但是不幸的是我尝试几次,返回地址没有被明显的直接覆盖,最后都会以崩溃掉。

好像直接观察堆栈并不能看出什么来,换个思路,完全从逆向的角度分析,先不断下断点定位崩溃函数,看函数崩溃的原因是什么。进过几次的断点后,定位到了ScStoragePathFromUrl内部的一个函数ScStripAndCheckHttpPrefix,这个函数代码如下。

没有错,这个函数调用了虚函数,那么这个洞是简单的堆溢出吗?
再次进行调试,这次进行大量的逆向数据记录,希望从数据的变化中分析出这个洞的成因到底是什么。
首先我们要确定一下我们的要关注的数据什么?(地址通过IDA得到)
[1]内存拷贝 代码地址 0x673F6F99
[2] 判断是否进行数据拷贝的代码地址 代码地址 0x673F6F55
于是windbg下断点,执行情况如下

可以发现这个数据拷贝并不会每次进入ScStoragePathFromUrl函数都会进行拷贝,而且拷贝次数只有3次,分别是第二调用ScStoragePathFromUrl函数,第三调用ScStoragePathFromUrl函数,第五调用ScStoragePathFromUrl函数(其实没进行拷贝的调用也很重要),好吧,既然拷贝了三次数据,那么就下断点进行查看到底拷贝了什么数据,对堆栈进行了什么的影响吧。

  再进入windbg,只对内存拷贝进行下断点,并且每次跟完全部的拷贝,
第一次拷贝的结果如下(代码太多,不全部展示)

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
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
.text:673F6F99 mov edi, [ebp-450]
//ebp=0130f798 ebp-450h=0130f348 [ebp-450h]=0130f804 edi=0130f804
.text:673F6F9F lea eax, [esi+esi]
// esi=00000012 eax=00000024
.text:673F6FA2 mov ecx, eax //ecx=00000024
.text:673F6FA4 mov edx, ecx //edx=00000024
.text:673F6FA6 shr ecx, 2 //ecx=00000009
.text:673F6FA9 lea esi, [ebp-43C]
//ebp-43Ch=0130f35c esi=0130f35ch
.text:673F6FAF rep movsd
.text:673F6FAF ; *
.text:673F6FAF ; * rep mov [edi],[esi] edi=0130f804 esi=0130f35c ebp=0130f798
.text:673F6FAF ; * ecx 的次数为9
.text:673F6FAF ; 结束时edi=0130f828 esi=0130f380
.text:673F6FAF ; /
.text:673F6FB1 mov ecx, edx
//edx=24h ecx=24h
.text:673F6FB3 mov edx, [ebp-22Ch]
//ebp= 0130f798 ebp-22Ch=0130f56c edx=[0130f56c]=0
.text:673F6FB9 and ecx, 3 //ecx=0
.text:673F6FBC rep movsb ; /
.text:673F6FBC ; *
.text:673F6FBC ; *ecx=0 没有拷贝
.text:673F6FBC ; *
.text:673F6FBC ; /
.text:673F6FBE mov esi, [ebp-444h]
// ebp-444h=0130f354 [ebp-444h]=0067cd38 esi=0067cd38
.text:673F6FC4 sub ebx, edx
//ebx=00000097h edx=00000000h ebx=00000097h
.text:673F6FC6 lea esi, [esi+edx*2] //esi=0067cd38
.text:673F6FC9 mov edx, [ebp-450h]
//ebp-450h=0130f34
//edx=0130f804
//[ebp-450h]=0130f804
.text:673F6FCF lea edi, [eax+edx]
//eax=24h edx=0130f804 eax+edx=0130f828
/
*
*注意这个eax+edx地址就是我们上面拷贝的edi结束数据地址
*所以edi=edi原来的结束地址
/
.text:673F6FD2 lea ecx, [ebx+ebx+2] //ecx=00000130h
.text:673F6FD6 mov eax, ecx //eax=00000130h
.text:673F6FD8 shr ecx, 2 //ecx=0000004ch
.text:673F6FDB rep movsd
; /esi=0067cd38h edi=0130f828
.text:673F6FDB ; *
.text:673F6FDB ; *
.text:673F6FDB ; *
.text:673F6FDB ; *拷贝前ecx=4c edi=0130f828 esi=0067cd38
.text:673F6FDB ; *拷贝后edi=0130f958=ebp+1C0h
.text:673F6FDB ; *esi=0067ce68
.text:673F6FDB ; /
.text:673F6FDD mov ecx, eax
.text:673F6FDF and ecx, 3
.text:673F6FE2 rep movsb ; /
.text:673F6FE2 ; *
.text:673F6FE2 ; *ecx=0 拷贝没有执行
.text:673F6FE2
; /

第一次拷贝的数据范围
edi 0130f804~0130f958

(第二次和第三次执行的时候edi的地址会大改,但是篇幅的原因,不能全部展示)
现在讲讲通过上面的代码再结合IDA,Windbg,我自己找到数据关系。
一共三次大内存拷贝,每次进行一次大内存拷贝时,有4次小拷贝,其中有两次不会拷贝,而另外进行拷贝的两次的地址edi是连续的,而esi是不连续的。
Edi的地址如果在某次大拷贝的时候变成一个对象的地址,然后进行拷贝,是不是就可以利用了呢?原作者也确实这么做了。
三次大内存拷贝的edi的地址变化范围如下:

第一次拷贝的数据edi:0x0130f804~0x0130f958
第二次拷贝的数据edi:0x680312c0~0x68031464
第三次拷贝的数据edi:0x0130fab4~0x0130fc08

通过数据好像看起来第一次和第三次好像是栈溢出。第二次是堆溢出
但是edi,esi的值是怎么被修改的,edi的值来自哪里?Esi的值来自哪里,这个问题需要弄明白。
通过IDA,和windbg,我找到了这些代码

1
2
3
4
5
6
7
8
edi 值来源[ebp-450] .text:673F6F99 mov edi, [ebp-450]
[ebp-450] 来源eax .text:673F6C9B mov [ebp-450], eax
eax 来源 [ebp+8] .text:673F6C93 mov eax,dword ptr [ebp+8]
/
*
*前两次的ebp没有变化都是0130f798
*所以ebp+8地址不变0130f7a0
/

通过看上面的第一次拷贝的范围可以知道,第二次开始的edi(堆地址)的来源地址0130f7a0,它并没有在第一次拷贝的范围内,但是这个地址就不能通过别的方法修改吗?
通过继续跟踪函数我找到了这条指令

1
2
3
4
5
eax=0130f800 ebx=0067b508 ecx=00675740 edx=0067ce78 esi=00000000 edi=77ba8ef2
eip=673e9461 esp=0130f7a4 ebp=0130f7ac 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!CMethUtil::ScStoragePathFromUrl+0x10:
673e9461 ff750c push dword ptr [ebp+0Ch] ss:0023:0130f7b8=680312c0

当前的esp=0130f7a4,push也就是在给0130f7a0赋值

而ebp+0Ch =0x0130f7b8来自于

1
2
3
4
5
eax=0067003c ebx=0067b508 ecx=00000000 edx=00000026 esi=0067cb38 edi=680312c0
eip=673f54df esp=0130f7bc ebp=0130fc34 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+0x1b9:
673f54df 57 push edi

Edi来自于

1
2
3
4
5
6
0:007>
eax=680312be ebx=0067b508 ecx=680312c0 edx=0130f804 esi=00000000 edi=77ba8ef2
eip=673f54b1 esp=0130f7c0 ebp=0130fc34 iopl=0 nv up ei pl nz ac po nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000212
httpext!HrCheckIfHeader+0x18b:
673f54b1 8bbdd8fcffff mov edi,dword ptr [ebp-328h] ss:0023:0130f90c=680312c0

跟到了这里原来数据来源是0130f90c,结合第一次拷贝的数据范围edi:0x0130f804~0x0130f958可以知道,这个地址就是拷贝进去的数据。(这里由于篇幅有限只分析,怎么通过第一次拷贝的数据,修改第二次将进行拷贝的edi的值,第二次的拷贝怎么修改第三次的edi值类推)

那么ESI是什么呢
通过几次再内存中的查看
[1]第一次拷贝前的esi指向的内存内容

[2]第二次拷贝前的esi指向的内存内容

[3]第三次拷贝前的esi指向的内存内容

可以知道esi的内容就只想我们发送数据的地址。
好了,我们现在能知道原作者通过第一次的溢出,使第二次的edi,变成自构造的对象堆地址,进行了第二次堆溢出,那么第三次溢出的作用是什么?
第三次拷贝的数据范围edi:0x0130fab4~0x0130fc08
下面这条指令是给ecx(this指针)赋值的,此时的ebp-14h=0130fbbc在第三次的溢出范围。而且结合第二次拷贝的数范围据edi:0x680312c0~0x68031464可以知道现在赋值的ecx就是第二次拷贝的起始地址。

//ecx值来源栈地址 0130f960内容 ->变成680312c0

1
673f578f ff75ec push dword ptr [ebp-14h] ss:0023:0130fbbc=680312c0

接下来在执行虚函数时ecx已经是构造的exp的地址

进入之后发现虚函数的内容如下

把ecx赋值给esp,retn后,就从对象开始执行,并进行ROP,绕过DEP

漏洞成因

最后拷贝数据的大小从哪里来的?
进行每次大拷贝的第二次拷贝的ecx作为循环的次数

1
2
3
4
.text:673F6FD2 ? ? ? ? ? ? ? ? lea ? ? ecx, [ebx+ebx+2]
.text:673F6FD6 ? ? ? ? ? ? ? ? mov ? ? eax, ecx ? //eax=00000130h
.text:673F6FD8 ? ? ? ? ? ? ? ? shr ? ? ecx, 2 ? ? //ecx=0000004ch
.text:673F6FDB ? ? ? ? ? ? ? ? rep movsd ? ? ? ? ? ? ? ; /esi=0067cd38h edi=0130f828

而ecx的值的来自于ebx(edx总为0)

1
.text:673F6FC4 ? ? ? ? ? ? ? ? sub ? ? ebx, edx

ebx来自eax

1
.text:673F6CF0 ? ? ? ? ? ? ? ? mov ? ? ebx, eax

eax来自 ds:__imp__wcslen返回值

1
2
3
;str 也就是我们每次大拷贝的第二次拷贝的esi的数据源
.text:673F6CE8 ? ? ? ? ? ? ? ? push ? ?eax ? ? ? ? ? ? ; Str
.text:673F6CE9 ? ? ? ? ? ? ? ? call ? ?ds:__imp__wcsle

所以可以看出它是以数据的大小作为我们拷贝数据的大小,并没有拷贝大小限制,导致溢出。

ps:分析了半天,如果说,它为什么了去了shellcode就会崩,其实崩的地址就是第二次拷贝的数据末尾+0x4,崩的原因是当执行到的第二次拷贝的数据末尾+0x4地址时,它的内容为0x0000,这个硬编码对应汇编代码

add byte ptr [eax],al
因为eax的值指向地址无效,所以崩溃。
其实这个位置本来该是shellcode的起始地址(执行到这里已经关闭DEP可以顺利执行数据代码了),,因为我们去掉了shellcode,并没有任何数据,以地址访问异常导致崩溃。