/* $Id: SUPHardenedVerifyProcess-win.cpp 106061 2024-09-16 14:03:52Z vboxsync $ */ /** @file * VirtualBox Support Library/Driver - Hardened Process Verification, Windows. */ /* * Copyright (C) 2006-2024 Oracle and/or its affiliates. * * This file is part of VirtualBox base platform packages, as * available from https://www.virtualbox.org. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation, in version 3 of the * License. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, see . * * The contents of this file may alternatively be used under the terms * of the Common Development and Distribution License Version 1.0 * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included * in the VirtualBox distribution, in which case the provisions of the * CDDL are applicable instead of those of the GPL. * * You may elect to license modified versions of this file under the * terms and conditions of either the GPL or the CDDL or both. * * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 */ /********************************************************************************************************************************* * Header Files * *********************************************************************************************************************************/ #ifdef IN_RING0 # ifndef IPRT_NT_MAP_TO_ZW # define IPRT_NT_MAP_TO_ZW # endif # include # include #else # include #endif #include #include #include #include #include #include #include #include #ifdef IN_RING0 # include "SUPDrvInternal.h" #else # include "SUPLibInternal.h" #endif #include "win/SUPHardenedVerify-win.h" /********************************************************************************************************************************* * Structures and Typedefs * *********************************************************************************************************************************/ /** * Virtual address space region. */ typedef struct SUPHNTVPREGION { /** The RVA of the region. */ uint32_t uRva; /** The size of the region. */ uint32_t cb; /** The protection of the region. */ uint32_t fProt; } SUPHNTVPREGION; /** Pointer to a virtual address space region. */ typedef SUPHNTVPREGION *PSUPHNTVPREGION; /** * Virtual address space image information. */ typedef struct SUPHNTVPIMAGE { /** The base address of the image. */ uintptr_t uImageBase; /** The size of the image mapping. */ uintptr_t cbImage; /** The name from the allowed lists. */ const char *pszName; /** Name structure for NtQueryVirtualMemory/MemorySectionName. */ struct { /** The full unicode name. */ UNICODE_STRING UniStr; /** Buffer space. */ WCHAR awcBuffer[260]; } Name; /** The number of mapping regions. */ uint32_t cRegions; /** Mapping regions. */ SUPHNTVPREGION aRegions[16]; /** The image characteristics from the FileHeader. */ uint16_t fImageCharecteristics; /** The DLL characteristics from the OptionalHeader. */ uint16_t fDllCharecteristics; /** Set if this is the DLL. */ bool fDll; /** Set if the image is NTDLL an the verficiation code needs to watch out for * the NtCreateSection patch. */ bool fNtCreateSectionPatch; /** Whether the API set schema hack needs to be applied when verifying memory * content. The hack means that we only check if the 1st section is mapped. */ bool fApiSetSchemaOnlySection1; /** This may be a 32-bit resource DLL. */ bool f32bitResourceDll; /** Pointer to the loader cache entry for the image. */ PSUPHNTLDRCACHEENTRY pCacheEntry; #ifdef IN_RING0 /** In ring-0 we don't currently cache images, so put it here. */ SUPHNTLDRCACHEENTRY CacheEntry; #endif } SUPHNTVPIMAGE; /** Pointer to image info from the virtual address space scan. */ typedef SUPHNTVPIMAGE *PSUPHNTVPIMAGE; /** * Virtual address space scanning state. */ typedef struct SUPHNTVPSTATE { /** Type of verification to perform. */ SUPHARDNTVPKIND enmKind; /** Combination of SUPHARDNTVP_F_XXX. */ uint32_t fFlags; /** The result. */ int rcResult; /** Number of fixes we've done. * Only applicable in the purification modes. */ uint32_t cFixes; /** Number of images in aImages. */ uint32_t cImages; /** The index of the last image we looked up. */ uint32_t iImageHint; /** The process handle. */ HANDLE hProcess; /** Images found in the process. * The array is large enough to hold the executable, all allowed DLLs, and one * more so we can get the image name of the first unwanted DLL. */ SUPHNTVPIMAGE aImages[1 + 6 + 1 #ifdef VBOX_PERMIT_VERIFIER_DLL + 1 #endif #ifdef VBOX_PERMIT_MORE + 5 #endif #ifdef VBOX_PERMIT_VISUAL_STUDIO_PROFILING + 16 #endif ]; /** Memory compare scratch buffer.*/ uint8_t abMemory[_4K]; /** File compare scratch buffer.*/ uint8_t abFile[_4K]; /** Section headers for use when comparing file and loaded image. */ IMAGE_SECTION_HEADER aSecHdrs[16]; /** Pointer to the error info. */ PRTERRINFO pErrInfo; } SUPHNTVPSTATE; /** Pointer to stat information of a virtual address space scan. */ typedef SUPHNTVPSTATE *PSUPHNTVPSTATE; /********************************************************************************************************************************* * Global Variables * *********************************************************************************************************************************/ /** * System DLLs allowed to be loaded into the process. * @remarks supHardNtVpCheckDlls assumes these are lower case. */ static const char *g_apszSupNtVpAllowedDlls[] = { "ntdll.dll", "kernel32.dll", "kernelbase.dll", "apphelp.dll", "apisetschema.dll", #ifdef VBOX_PERMIT_VERIFIER_DLL "verifier.dll", #endif #ifdef VBOX_PERMIT_MORE # define VBOX_PERMIT_MORE_FIRST_IDX 5 "sfc.dll", "sfc_os.dll", "user32.dll", "acres.dll", "acgenral.dll", #endif #ifdef VBOX_PERMIT_VISUAL_STUDIO_PROFILING "psapi.dll", "msvcrt.dll", "advapi32.dll", "sechost.dll", "rpcrt4.dll", "SamplingRuntime.dll", #endif }; /** * VBox executables allowed to start VMs. * @remarks Remember to keep in sync with g_aSupInstallFiles in * SUPR3HardenedVerify.cpp. */ static const char *g_apszSupNtVpAllowedVmExes[] = { "VBoxHeadless.exe", "VirtualBoxVM.exe", "VBoxSDL.exe", "VBoxNetDHCP.exe", "VBoxNetNAT.exe", "VBoxVMMPreload.exe", "tstMicro.exe", "tstPDMAsyncCompletion.exe", "tstPDMAsyncCompletionStress.exe", "tstVMM.exe", "tstVMREQ.exe", "tstCFGM.exe", "tstGIP-2.exe", "tstIntNet-1.exe", "tstMMHyperHeap.exe", "tstRTR0ThreadPreemptionDriver.exe", "tstRTR0MemUserKernelDriver.exe", "tstRTR0SemMutexDriver.exe", "tstRTR0TimerDriver.exe", "tstSSM.exe", }; /** Pointer to NtQueryVirtualMemory. Initialized by SUPDrv-win.cpp in * ring-0, in ring-3 it's just a slightly confusing define. */ #ifdef IN_RING0 PFNNTQUERYVIRTUALMEMORY g_pfnNtQueryVirtualMemory = NULL; #else # define g_pfnNtQueryVirtualMemory NtQueryVirtualMemory #endif #ifdef IN_RING3 /** The number of valid entries in the loader cache. */ static uint32_t g_cSupNtVpLdrCacheEntries = 0; /** The loader cache entries. */ static SUPHNTLDRCACHEENTRY g_aSupNtVpLdrCacheEntries[RT_ELEMENTS(g_apszSupNtVpAllowedDlls) + 1 + 3]; #endif /** * Fills in error information. * * @returns @a rc. * @param pErrInfo Pointer to the extended error info structure. * Can be NULL. * @param rc The status to return. * @param pszMsg The format string for the message. * @param ... The arguments for the format string. */ static int supHardNtVpSetInfo1(PRTERRINFO pErrInfo, int rc, const char *pszMsg, ...) { va_list va; #ifdef IN_RING3 va_start(va, pszMsg); supR3HardenedError(rc, false /*fFatal*/, "%N\n", pszMsg, &va); va_end(va); #endif va_start(va, pszMsg); RTErrInfoSetV(pErrInfo, rc, pszMsg, va); va_end(va); return rc; } /** * Adds error information. * * @returns @a rc. * @param pErrInfo Pointer to the extended error info structure * which may contain some details already. Can be * NULL. * @param rc The status to return. * @param pszMsg The format string for the message. * @param ... The arguments for the format string. */ static int supHardNtVpAddInfo1(PRTERRINFO pErrInfo, int rc, const char *pszMsg, ...) { va_list va; #ifdef IN_RING3 va_start(va, pszMsg); if (pErrInfo && pErrInfo->pszMsg) supR3HardenedError(rc, false /*fFatal*/, "%N - %s\n", pszMsg, &va, pErrInfo->pszMsg); else supR3HardenedError(rc, false /*fFatal*/, "%N\n", pszMsg, &va); va_end(va); #endif va_start(va, pszMsg); RTErrInfoAddV(pErrInfo, rc, pszMsg, va); va_end(va); return rc; } /** * Fills in error information. * * @returns @a rc. * @param pThis The process validator instance. * @param rc The status to return. * @param pszMsg The format string for the message. * @param ... The arguments for the format string. */ static int supHardNtVpSetInfo2(PSUPHNTVPSTATE pThis, int rc, const char *pszMsg, ...) { va_list va; #ifdef IN_RING3 va_start(va, pszMsg); supR3HardenedError(rc, false /*fFatal*/, "%N\n", pszMsg, &va); va_end(va); #endif va_start(va, pszMsg); #ifdef IN_RING0 RTErrInfoSetV(pThis->pErrInfo, rc, pszMsg, va); pThis->rcResult = rc; #else if (RT_SUCCESS(pThis->rcResult)) { RTErrInfoSetV(pThis->pErrInfo, rc, pszMsg, va); pThis->rcResult = rc; } else { RTErrInfoAddF(pThis->pErrInfo, rc, " \n[rc=%d] ", rc); RTErrInfoAddV(pThis->pErrInfo, rc, pszMsg, va); } #endif va_end(va); return pThis->rcResult; } static int supHardNtVpReadImage(PSUPHNTVPIMAGE pImage, uint64_t off, void *pvBuf, size_t cbRead) { return pImage->pCacheEntry->pNtViRdr->Core.pfnRead(&pImage->pCacheEntry->pNtViRdr->Core, pvBuf, cbRead, off); } static NTSTATUS supHardNtVpReadMem(HANDLE hProcess, uintptr_t uPtr, void *pvBuf, size_t cbRead) { #ifdef IN_RING0 /* ASSUMES hProcess is the current process. */ RT_NOREF1(hProcess); /** @todo use MmCopyVirtualMemory where available! */ int rc = RTR0MemUserCopyFrom(pvBuf, uPtr, cbRead); if (RT_SUCCESS(rc)) return STATUS_SUCCESS; return STATUS_ACCESS_DENIED; #else SIZE_T cbIgn; NTSTATUS rcNt = NtReadVirtualMemory(hProcess, (PVOID)uPtr, pvBuf, cbRead, &cbIgn); if (NT_SUCCESS(rcNt) && cbIgn != cbRead) rcNt = STATUS_IO_DEVICE_ERROR; return rcNt; #endif } #ifdef IN_RING3 static NTSTATUS supHardNtVpFileMemRestore(PSUPHNTVPSTATE pThis, PVOID pvRestoreAddr, uint8_t const *pbFile, uint32_t cbToRestore, uint32_t fCorrectProtection) { PVOID pvProt = pvRestoreAddr; SIZE_T cbProt = cbToRestore; ULONG fOldProt = 0; NTSTATUS rcNt = NtProtectVirtualMemory(pThis->hProcess, &pvProt, &cbProt, PAGE_READWRITE, &fOldProt); if (NT_SUCCESS(rcNt)) { SIZE_T cbIgnored; rcNt = NtWriteVirtualMemory(pThis->hProcess, pvRestoreAddr, pbFile, cbToRestore, &cbIgnored); pvProt = pvRestoreAddr; cbProt = cbToRestore; NTSTATUS rcNt2 = NtProtectVirtualMemory(pThis->hProcess, &pvProt, &cbProt, fCorrectProtection, &fOldProt); if (NT_SUCCESS(rcNt)) rcNt = rcNt2; } pThis->cFixes++; return rcNt; } #endif /* IN_RING3 */ typedef struct SUPHNTVPSKIPAREA { uint32_t uRva; uint32_t cb; } SUPHNTVPSKIPAREA; typedef SUPHNTVPSKIPAREA *PSUPHNTVPSKIPAREA; static int supHardNtVpFileMemCompareSection(PSUPHNTVPSTATE pThis, PSUPHNTVPIMAGE pImage, uint32_t uRva, uint32_t cb, const uint8_t *pbFile, int32_t iSh, PSUPHNTVPSKIPAREA paSkipAreas, uint32_t cSkipAreas, uint32_t fCorrectProtection) { #ifndef IN_RING3 RT_NOREF1(fCorrectProtection); #endif AssertCompileAdjacentMembers(SUPHNTVPSTATE, abMemory, abFile); /* Use both the memory and file buffers here. Parfait might hate me for this... */ uint32_t const cbMemory = sizeof(pThis->abMemory) + sizeof(pThis->abFile); uint8_t * const pbMemory = &pThis->abMemory[0]; while (cb > 0) { uint32_t cbThis = RT_MIN(cb, cbMemory); /* Clipping. */ uint32_t uNextRva = uRva + cbThis; if (cSkipAreas) { uint32_t uRvaEnd = uNextRva; uint32_t i = cSkipAreas; while (i-- > 0) { uint32_t uSkipEnd = paSkipAreas[i].uRva + paSkipAreas[i].cb; if ( uRva < uSkipEnd && uRvaEnd > paSkipAreas[i].uRva) { if (uRva < paSkipAreas[i].uRva) { cbThis = paSkipAreas[i].uRva - uRva; uRvaEnd = paSkipAreas[i].uRva; uNextRva = uSkipEnd; } else if (uRvaEnd >= uSkipEnd) { cbThis -= uSkipEnd - uRva; pbFile += uSkipEnd - uRva; uRva = uSkipEnd; } else { uNextRva = uSkipEnd; cbThis = 0; break; } } } } /* Read the memory. */ NTSTATUS rcNt = supHardNtVpReadMem(pThis->hProcess, pImage->uImageBase + uRva, pbMemory, cbThis); if (!NT_SUCCESS(rcNt)) return supHardNtVpSetInfo2(pThis, VERR_SUP_VP_MEMORY_READ_ERROR, "%s: Error reading %#x bytes at %p (rva %#x, #%u, %.8s) from memory: %#x", pImage->pszName, cbThis, pImage->uImageBase + uRva, uRva, iSh + 1, iSh >= 0 ? (char *)pThis->aSecHdrs[iSh].Name : "headers", rcNt); /* Do the compare. */ if (memcmp(pbFile, pbMemory, cbThis) != 0) { const char *pachSectNm = iSh >= 0 ? (char *)pThis->aSecHdrs[iSh].Name : "headers"; SUP_DPRINTF(("%s: Differences in section #%u (%s) between file and memory:\n", pImage->pszName, iSh + 1, pachSectNm)); uint32_t off = 0; while (off < cbThis && pbFile[off] == pbMemory[off]) off++; SUP_DPRINTF((" %p / %#09x: %02x != %02x\n", pImage->uImageBase + uRva + off, uRva + off, pbFile[off], pbMemory[off])); uint32_t offLast = off; uint32_t cDiffs = 1; for (uint32_t off2 = off + 1; off2 < cbThis; off2++) if (pbFile[off2] != pbMemory[off2]) { SUP_DPRINTF((" %p / %#09x: %02x != %02x\n", pImage->uImageBase + uRva + off2, uRva + off2, pbFile[off2], pbMemory[off2])); cDiffs++; offLast = off2; } #ifdef IN_RING3 if ( pThis->enmKind == SUPHARDNTVPKIND_CHILD_PURIFICATION || pThis->enmKind == SUPHARDNTVPKIND_SELF_PURIFICATION || pThis->enmKind == SUPHARDNTVPKIND_SELF_PURIFICATION_LIMITED) { PVOID pvRestoreAddr = (uint8_t *)pImage->uImageBase + uRva; rcNt = supHardNtVpFileMemRestore(pThis, pvRestoreAddr, pbFile, cbThis, fCorrectProtection); if (NT_SUCCESS(rcNt)) SUP_DPRINTF((" Restored %#x bytes of original file content at %p\n", cbThis, pvRestoreAddr)); else return supHardNtVpSetInfo2(pThis, VERR_SUP_VP_MEMORY_VS_FILE_MISMATCH, "%s: Failed to restore %#x bytes at %p (%#x, #%u, %s): %#x (cDiffs=%#x, first=%#x)", pImage->pszName, cbThis, pvRestoreAddr, uRva, iSh + 1, pachSectNm, rcNt, cDiffs, uRva + off); } else #endif /* IN_RING3 */ return supHardNtVpSetInfo2(pThis, VERR_SUP_VP_MEMORY_VS_FILE_MISMATCH, "%s: %u differences between %#x and %#x in #%u (%.8s), first: %02x != %02x", pImage->pszName, cDiffs, uRva + off, uRva + offLast, iSh + 1, pachSectNm, pbFile[off], pbMemory[off]); } /* Advance. The clipping makes it a little bit complicated. */ cbThis = uNextRva - uRva; if (cbThis >= cb) break; cb -= cbThis; pbFile += cbThis; uRva = uNextRva; } return VINF_SUCCESS; } static int supHardNtVpCheckSectionProtection(PSUPHNTVPSTATE pThis, PSUPHNTVPIMAGE pImage, uint32_t uRva, uint32_t cb, uint32_t fProt) { uint32_t const cbOrg = cb; if (!cb) return VINF_SUCCESS; if ( pThis->enmKind == SUPHARDNTVPKIND_CHILD_PURIFICATION || pThis->enmKind == SUPHARDNTVPKIND_SELF_PURIFICATION || pThis->enmKind == SUPHARDNTVPKIND_SELF_PURIFICATION_LIMITED) return VINF_SUCCESS; for (uint32_t i = 0; i < pImage->cRegions; i++) { uint32_t offRegion = uRva - pImage->aRegions[i].uRva; if (offRegion < pImage->aRegions[i].cb) { uint32_t cbLeft = pImage->aRegions[i].cb - offRegion; if ( pImage->aRegions[i].fProt != fProt && ( fProt != PAGE_READWRITE || pImage->aRegions[i].fProt != PAGE_WRITECOPY)) return supHardNtVpSetInfo2(pThis, VERR_SUP_VP_SECTION_PROTECTION_MISMATCH, "%s: RVA range %#x-%#x protection is %#x, expected %#x. (cb=%#x)", pImage->pszName, uRva, uRva + cbLeft - 1, pImage->aRegions[i].fProt, fProt, cb); if (cbLeft >= cb) return VINF_SUCCESS; cb -= cbLeft; uRva += cbLeft; #if 0 /* This shouldn't ever be necessary. */ if ( i + 1 < pImage->cRegions && uRva < pImage->aRegions[i + 1].uRva) { cbLeft = pImage->aRegions[i + 1].uRva - uRva; if (cbLeft >= cb) return VINF_SUCCESS; cb -= cbLeft; uRva += cbLeft; } #endif } } return supHardNtVpSetInfo2(pThis, cbOrg == cb ? VERR_SUP_VP_SECTION_NOT_MAPPED : VERR_SUP_VP_SECTION_NOT_FULLY_MAPPED, "%s: RVA range %#x-%#x is not mapped?", pImage->pszName, uRva, uRva + cb - 1); } DECLINLINE(bool) supHardNtVpIsModuleNameMatch(PSUPHNTVPIMAGE pImage, const char *pszModule) { if (pImage->fDll) { const char *pszImageNm = pImage->pszName; for (;;) { char chLeft = *pszImageNm++; char chRight = *pszModule++; if (chLeft != chRight) { Assert(chLeft == RT_C_TO_LOWER(chLeft)); if (chLeft != RT_C_TO_LOWER(chRight)) { if ( chRight == '\0' && chLeft == '.' && pszImageNm[0] == 'd' && pszImageNm[1] == 'l' && pszImageNm[2] == 'l' && pszImageNm[3] == '\0') return true; break; } } if (chLeft == '\0') return true; } } return false; } /** * Worker for supHardNtVpGetImport that looks up a module in the module table. * * @returns Pointer to the module if found, NULL if not found. * @param pThis The process validator instance. * @param pszModule The name of the module we're looking for. */ static PSUPHNTVPIMAGE supHardNtVpFindModule(PSUPHNTVPSTATE pThis, const char *pszModule) { /* * Check out the hint first. */ if ( pThis->iImageHint < pThis->cImages && supHardNtVpIsModuleNameMatch(&pThis->aImages[pThis->iImageHint], pszModule)) return &pThis->aImages[pThis->iImageHint]; /* * Linear array search next. */ uint32_t i = pThis->cImages; while (i-- > 0) if (supHardNtVpIsModuleNameMatch(&pThis->aImages[i], pszModule)) { pThis->iImageHint = i; return &pThis->aImages[i]; } /* No cigar. */ return NULL; } /** * @callback_method_impl{FNRTLDRIMPORT} */ static DECLCALLBACK(int) supHardNtVpGetImport(RTLDRMOD hLdrMod, const char *pszModule, const char *pszSymbol, unsigned uSymbol, PRTLDRADDR pValue, void *pvUser) { RT_NOREF1(hLdrMod); /*SUP_DPRINTF(("supHardNtVpGetImport: %s / %#x / %s.\n", pszModule, uSymbol, pszSymbol));*/ PSUPHNTVPSTATE pThis = (PSUPHNTVPSTATE)pvUser; int rc = VERR_MODULE_NOT_FOUND; PSUPHNTVPIMAGE pImage = supHardNtVpFindModule(pThis, pszModule); if (pImage) { rc = RTLdrGetSymbolEx(pImage->pCacheEntry->hLdrMod, pImage->pCacheEntry->pbBits, pImage->uImageBase, uSymbol, pszSymbol, pValue); if (RT_SUCCESS(rc)) return rc; } /* * API set hacks. */ else if (!RTStrNICmp(pszModule, RT_STR_TUPLE("api-ms-win-"))) { static const char * const s_apszDlls[] = { "ntdll.dll", "kernelbase.dll", "kernel32.dll" }; for (uint32_t i = 0; i < RT_ELEMENTS(s_apszDlls); i++) { pImage = supHardNtVpFindModule(pThis, s_apszDlls[i]); if (pImage) { rc = RTLdrGetSymbolEx(pImage->pCacheEntry->hLdrMod, pImage->pCacheEntry->pbBits, pImage->uImageBase, uSymbol, pszSymbol, pValue); if (RT_SUCCESS(rc)) return rc; if (rc != VERR_SYMBOL_NOT_FOUND) break; } } } /* * Deal with forwarders. * ASSUMES no forwarders thru any api-ms-win-core-*.dll. * ASSUMES forwarders are resolved after one redirection. */ if (rc == VERR_LDR_FORWARDER) { size_t cbInfo = RT_MIN((uint32_t)*pValue, sizeof(RTLDRIMPORTINFO) + 32); PRTLDRIMPORTINFO pInfo = (PRTLDRIMPORTINFO)alloca(cbInfo); rc = RTLdrQueryForwarderInfo(pImage->pCacheEntry->hLdrMod, pImage->pCacheEntry->pbBits, uSymbol, pszSymbol, pInfo, cbInfo); if (RT_SUCCESS(rc)) { rc = VERR_MODULE_NOT_FOUND; pImage = supHardNtVpFindModule(pThis, pInfo->szModule); if (pImage) { rc = RTLdrGetSymbolEx(pImage->pCacheEntry->hLdrMod, pImage->pCacheEntry->pbBits, pImage->uImageBase, pInfo->iOrdinal, pInfo->pszSymbol, pValue); if (RT_SUCCESS(rc)) return rc; SUP_DPRINTF(("supHardNtVpGetImport: Failed to find symbol '%s' in '%s' (forwarded from %s / %s): %Rrc\n", pInfo->pszSymbol, pInfo->szModule, pszModule, pszSymbol, rc)); if (rc == VERR_LDR_FORWARDER) rc = VERR_LDR_FORWARDER_CHAIN_TOO_LONG; } else SUP_DPRINTF(("supHardNtVpGetImport: Failed to find forwarder module '%s' (%#x / %s; originally %s / %#x / %s): %Rrc\n", pInfo->szModule, pInfo->iOrdinal, pInfo->pszSymbol, pszModule, uSymbol, pszSymbol, rc)); } else SUP_DPRINTF(("supHardNtVpGetImport: RTLdrQueryForwarderInfo failed on symbol %#x/'%s' in '%s': %Rrc\n", uSymbol, pszSymbol, pszModule, rc)); } else SUP_DPRINTF(("supHardNtVpGetImport: Failed to find symbol %#x / '%s' in '%s': %Rrc\n", uSymbol, pszSymbol, pszModule, rc)); return rc; } /** * Compares process memory with the disk content. * * @returns VBox status code. * @param pThis The process scanning state structure (for the * two scratch buffers). * @param pImage The image data collected during the address * space scan. */ static int supHardNtVpVerifyImageMemoryCompare(PSUPHNTVPSTATE pThis, PSUPHNTVPIMAGE pImage) { /* * Read and find the file headers. */ int rc = supHardNtVpReadImage(pImage, 0 /*off*/, pThis->abFile, sizeof(pThis->abFile)); if (RT_FAILURE(rc)) return supHardNtVpSetInfo2(pThis, VERR_SUP_VP_IMAGE_HDR_READ_ERROR, "%s: Error reading image header: %Rrc", pImage->pszName, rc); uint32_t offNtHdrs = 0; PIMAGE_DOS_HEADER pDosHdr = (PIMAGE_DOS_HEADER)&pThis->abFile[0]; if (pDosHdr->e_magic == IMAGE_DOS_SIGNATURE) { offNtHdrs = pDosHdr->e_lfanew; if (offNtHdrs > 512 || offNtHdrs < sizeof(*pDosHdr)) return supHardNtVpSetInfo2(pThis, VERR_SUP_VP_BAD_MZ_OFFSET, "%s: Unexpected e_lfanew value: %#x", pImage->pszName, offNtHdrs); } PIMAGE_NT_HEADERS pNtHdrs = (PIMAGE_NT_HEADERS)&pThis->abFile[offNtHdrs]; PIMAGE_NT_HEADERS32 pNtHdrs32 = (PIMAGE_NT_HEADERS32)pNtHdrs; if (pNtHdrs->Signature != IMAGE_NT_SIGNATURE) return supHardNtVpSetInfo2(pThis, VERR_SUP_VP_BAD_IMAGE_SIGNATURE, "%s: No PE signature at %#x: %#x", pImage->pszName, offNtHdrs, pNtHdrs->Signature); /* * Do basic header validation. */ #ifdef RT_ARCH_AMD64 if (pNtHdrs->FileHeader.Machine != IMAGE_FILE_MACHINE_AMD64 && !pImage->f32bitResourceDll) #else if (pNtHdrs->FileHeader.Machine != IMAGE_FILE_MACHINE_I386) #endif return supHardNtVpSetInfo2(pThis, VERR_SUP_VP_UNEXPECTED_IMAGE_MACHINE, "%s: Unexpected machine: %#x", pImage->pszName, pNtHdrs->FileHeader.Machine); bool const fIs32Bit = pNtHdrs->FileHeader.Machine == IMAGE_FILE_MACHINE_I386; if (pNtHdrs->FileHeader.SizeOfOptionalHeader != (fIs32Bit ? sizeof(IMAGE_OPTIONAL_HEADER32) : sizeof(IMAGE_OPTIONAL_HEADER64))) return supHardNtVpSetInfo2(pThis, VERR_SUP_VP_BAD_OPTIONAL_HEADER, "%s: Unexpected optional header size: %#x", pImage->pszName, pNtHdrs->FileHeader.SizeOfOptionalHeader); if (pNtHdrs->OptionalHeader.Magic != (fIs32Bit ? IMAGE_NT_OPTIONAL_HDR32_MAGIC : IMAGE_NT_OPTIONAL_HDR64_MAGIC)) return supHardNtVpSetInfo2(pThis, VERR_SUP_VP_BAD_OPTIONAL_HEADER, "%s: Unexpected optional header magic: %#x", pImage->pszName, pNtHdrs->OptionalHeader.Magic); uint32_t cDirs = (fIs32Bit ? pNtHdrs32->OptionalHeader.NumberOfRvaAndSizes : pNtHdrs->OptionalHeader.NumberOfRvaAndSizes); if (cDirs != IMAGE_NUMBEROF_DIRECTORY_ENTRIES) return supHardNtVpSetInfo2(pThis, VERR_SUP_VP_BAD_OPTIONAL_HEADER, "%s: Unexpected data dirs: %#x", pImage->pszName, cDirs); /* * Before we start comparing things, store what we need to know from the headers. */ uint32_t const cSections = pNtHdrs->FileHeader.NumberOfSections; if (cSections > RT_ELEMENTS(pThis->aSecHdrs)) return supHardNtVpSetInfo2(pThis, VERR_SUP_VP_TOO_MANY_SECTIONS, "%s: Too many section headers: %#x", pImage->pszName, cSections); suplibHardenedMemCopy(pThis->aSecHdrs, (fIs32Bit ? (void *)(pNtHdrs32 + 1) : (void *)(pNtHdrs + 1)), cSections * sizeof(IMAGE_SECTION_HEADER)); uintptr_t const uImageBase = fIs32Bit ? pNtHdrs32->OptionalHeader.ImageBase : pNtHdrs->OptionalHeader.ImageBase; if (uImageBase & PAGE_OFFSET_MASK) return supHardNtVpSetInfo2(pThis, VERR_SUP_VP_BAD_IMAGE_BASE, "%s: Invalid image base: %p", pImage->pszName, uImageBase); uint32_t const cbImage = fIs32Bit ? pNtHdrs32->OptionalHeader.SizeOfImage : pNtHdrs->OptionalHeader.SizeOfImage; if (RT_ALIGN_32(pImage->cbImage, PAGE_SIZE) != RT_ALIGN_32(cbImage, PAGE_SIZE) && !pImage->fApiSetSchemaOnlySection1) return supHardNtVpSetInfo2(pThis, VERR_SUP_VP_BAD_IMAGE_SIZE, "%s: SizeOfImage (%#x) isn't close enough to the mapping size (%#x)", pImage->pszName, cbImage, pImage->cbImage); if (cbImage != RTLdrSize(pImage->pCacheEntry->hLdrMod)) return supHardNtVpSetInfo2(pThis, VERR_SUP_VP_BAD_IMAGE_SIZE, "%s: SizeOfImage (%#x) differs from what RTLdrSize returns (%#zx)", pImage->pszName, cbImage, RTLdrSize(pImage->pCacheEntry->hLdrMod)); uint32_t const cbSectAlign = fIs32Bit ? pNtHdrs32->OptionalHeader.SectionAlignment : pNtHdrs->OptionalHeader.SectionAlignment; if ( !RT_IS_POWER_OF_TWO(cbSectAlign) || cbSectAlign < PAGE_SIZE || cbSectAlign > (pImage->fApiSetSchemaOnlySection1 ? _64K : (uint32_t)PAGE_SIZE) ) return supHardNtVpSetInfo2(pThis, VERR_SUP_VP_BAD_SECTION_ALIGNMENT_VALUE, "%s: Unexpected SectionAlignment value: %#x", pImage->pszName, cbSectAlign); uint32_t const cbFileAlign = fIs32Bit ? pNtHdrs32->OptionalHeader.FileAlignment : pNtHdrs->OptionalHeader.FileAlignment; if (!RT_IS_POWER_OF_TWO(cbFileAlign) || cbFileAlign < 512 || cbFileAlign > PAGE_SIZE || cbFileAlign > cbSectAlign) return supHardNtVpSetInfo2(pThis, VERR_SUP_VP_BAD_FILE_ALIGNMENT_VALUE, "%s: Unexpected FileAlignment value: %#x (cbSectAlign=%#x)", pImage->pszName, cbFileAlign, cbSectAlign); uint32_t const cbHeaders = fIs32Bit ? pNtHdrs32->OptionalHeader.SizeOfHeaders : pNtHdrs->OptionalHeader.SizeOfHeaders; uint32_t const cbMinHdrs = offNtHdrs + (fIs32Bit ? sizeof(*pNtHdrs32) : sizeof(*pNtHdrs) ) + sizeof(IMAGE_SECTION_HEADER) * cSections; if (cbHeaders < cbMinHdrs) return supHardNtVpSetInfo2(pThis, VERR_SUP_VP_BAD_SIZE_OF_HEADERS, "%s: Headers are too small: %#x < %#x (cSections=%#x)", pImage->pszName, cbHeaders, cbMinHdrs, cSections); uint32_t const cbHdrsFile = RT_ALIGN_32(cbHeaders, cbFileAlign); if (cbHdrsFile > sizeof(pThis->abFile)) return supHardNtVpSetInfo2(pThis, VERR_SUP_VP_BAD_SIZE_OF_HEADERS, "%s: Headers are larger than expected: %#x/%#x (expected max %zx)", pImage->pszName, cbHeaders, cbHdrsFile, sizeof(pThis->abFile)); /* * Save some header fields we might be using later on. */ pImage->fImageCharecteristics = pNtHdrs->FileHeader.Characteristics; pImage->fDllCharecteristics = fIs32Bit ? pNtHdrs32->OptionalHeader.DllCharacteristics : pNtHdrs->OptionalHeader.DllCharacteristics; /* * Correct the apisetschema image base, size and region rva. */ if (pImage->fApiSetSchemaOnlySection1) { pImage->uImageBase -= pThis->aSecHdrs[0].VirtualAddress; pImage->cbImage += pThis->aSecHdrs[0].VirtualAddress; pImage->aRegions[0].uRva = pThis->aSecHdrs[0].VirtualAddress; } /* * Get relocated bits. */ uint8_t *pbBits; if (pThis->enmKind == SUPHARDNTVPKIND_CHILD_PURIFICATION) rc = supHardNtLdrCacheEntryGetBits(pImage->pCacheEntry, &pbBits, pImage->uImageBase, NULL /*pfnGetImport*/, pThis, pThis->pErrInfo); else rc = supHardNtLdrCacheEntryGetBits(pImage->pCacheEntry, &pbBits, pImage->uImageBase, supHardNtVpGetImport, pThis, pThis->pErrInfo); if (RT_FAILURE(rc)) return rc; /* XP SP3 does not set ImageBase to load address. It fixes up the image on load time though. */ if (g_uNtVerCombined >= SUP_NT_VER_VISTA) { if (fIs32Bit) ((PIMAGE_NT_HEADERS32)&pbBits[offNtHdrs])->OptionalHeader.ImageBase = (uint32_t)pImage->uImageBase; else ((PIMAGE_NT_HEADERS)&pbBits[offNtHdrs])->OptionalHeader.ImageBase = pImage->uImageBase; } /* * Figure out areas we should skip during comparison. */ uint32_t cSkipAreas = 0; SUPHNTVPSKIPAREA aSkipAreas[7]; if (pImage->fNtCreateSectionPatch) { RTLDRADDR uValue; if (pThis->enmKind == SUPHARDNTVPKIND_VERIFY_ONLY) { /* Ignore our NtCreateSection hack. */ rc = RTLdrGetSymbolEx(pImage->pCacheEntry->hLdrMod, pbBits, 0, UINT32_MAX, "NtCreateSection", &uValue); if (RT_FAILURE(rc)) return supHardNtVpSetInfo2(pThis, rc, "%s: Failed to find 'NtCreateSection': %Rrc", pImage->pszName, rc); aSkipAreas[cSkipAreas].uRva = (uint32_t)uValue; aSkipAreas[cSkipAreas++].cb = ARCH_BITS == 32 ? 5 : 12; /* Ignore our LdrLoadDll hack. */ rc = RTLdrGetSymbolEx(pImage->pCacheEntry->hLdrMod, pbBits, 0, UINT32_MAX, "LdrLoadDll", &uValue); if (RT_FAILURE(rc)) return supHardNtVpSetInfo2(pThis, rc, "%s: Failed to find 'LdrLoadDll': %Rrc", pImage->pszName, rc); aSkipAreas[cSkipAreas].uRva = (uint32_t)uValue; aSkipAreas[cSkipAreas++].cb = ARCH_BITS == 32 ? 5 : 12; } /* Ignore our patched LdrInitializeThunk hack. */ rc = RTLdrGetSymbolEx(pImage->pCacheEntry->hLdrMod, pbBits, 0, UINT32_MAX, "LdrInitializeThunk", &uValue); if (RT_FAILURE(rc)) return supHardNtVpSetInfo2(pThis, rc, "%s: Failed to find 'LdrInitializeThunk': %Rrc", pImage->pszName, rc); aSkipAreas[cSkipAreas].uRva = (uint32_t)uValue; aSkipAreas[cSkipAreas++].cb = 14; /* Ignore our patched KiUserApcDispatcher hack. */ rc = RTLdrGetSymbolEx(pImage->pCacheEntry->hLdrMod, pbBits, 0, UINT32_MAX, "KiUserApcDispatcher", &uValue); if (RT_FAILURE(rc)) return supHardNtVpSetInfo2(pThis, rc, "%s: Failed to find 'KiUserApcDispatcher': %Rrc", pImage->pszName, rc); aSkipAreas[cSkipAreas].uRva = (uint32_t)uValue; aSkipAreas[cSkipAreas++].cb = 14; #ifndef VBOX_WITHOUT_HARDENDED_XCPT_LOGGING /* Ignore our patched KiUserExceptionDispatcher hack. */ rc = RTLdrGetSymbolEx(pImage->pCacheEntry->hLdrMod, pbBits, 0, UINT32_MAX, "KiUserExceptionDispatcher", &uValue); if (RT_FAILURE(rc)) return supHardNtVpSetInfo2(pThis, rc, "%s: Failed to find 'KiUserExceptionDispatcher': %Rrc", pImage->pszName, rc); aSkipAreas[cSkipAreas].uRva = (uint32_t)uValue + (HC_ARCH_BITS == 64); aSkipAreas[cSkipAreas++].cb = HC_ARCH_BITS == 64 ? 13 : 12; #endif /* LdrSystemDllInitBlock is filled in by the kernel. It mainly contains addresses of 32-bit ntdll method for wow64. */ rc = RTLdrGetSymbolEx(pImage->pCacheEntry->hLdrMod, pbBits, 0, UINT32_MAX, "LdrSystemDllInitBlock", &uValue); if (RT_SUCCESS(rc)) { aSkipAreas[cSkipAreas].uRva = (uint32_t)uValue; aSkipAreas[cSkipAreas++].cb = RT_MAX(pbBits[(uint32_t)uValue], 0x50); } Assert(cSkipAreas <= RT_ELEMENTS(aSkipAreas)); } /* * Compare the file header with the loaded bits. The loader will fiddle * with image base, changing it to the actual load address. */ if (!pImage->fApiSetSchemaOnlySection1) { rc = supHardNtVpFileMemCompareSection(pThis, pImage, 0 /*uRva*/, cbHdrsFile, pbBits, -1, NULL, 0, PAGE_READONLY); if (RT_FAILURE(rc)) return rc; rc = supHardNtVpCheckSectionProtection(pThis, pImage, 0 /*uRva*/, cbHdrsFile, PAGE_READONLY); if (RT_FAILURE(rc)) return rc; } /* * Validate sections: * - Check them against the mapping regions. * - Check section bits according to enmKind. */ uint32_t fPrevProt = PAGE_READONLY; uint32_t uRva = cbHdrsFile; for (uint32_t i = 0; i < cSections; i++) { /* Validate the section. */ uint32_t uSectRva = pThis->aSecHdrs[i].VirtualAddress; if (uSectRva < uRva || uSectRva > cbImage || RT_ALIGN_32(uSectRva, cbSectAlign) != uSectRva) return supHardNtVpSetInfo2(pThis, VERR_SUP_VP_BAD_SECTION_RVA, "%s: Section %u: Invalid virtual address: %#x (uRva=%#x, cbImage=%#x, cbSectAlign=%#x)", pImage->pszName, i, uSectRva, uRva, cbImage, cbSectAlign); uint32_t cbMap = pThis->aSecHdrs[i].Misc.VirtualSize; if (cbMap > cbImage || uRva + cbMap > cbImage) return supHardNtVpSetInfo2(pThis, VERR_SUP_VP_BAD_SECTION_VIRTUAL_SIZE, "%s: Section %u: Invalid virtual size: %#x (uSectRva=%#x, uRva=%#x, cbImage=%#x)", pImage->pszName, i, cbMap, uSectRva, uRva, cbImage); uint32_t cbFile = pThis->aSecHdrs[i].SizeOfRawData; if (cbFile != RT_ALIGN_32(cbFile, cbFileAlign) || cbFile > RT_ALIGN_32(cbMap, cbSectAlign)) return supHardNtVpSetInfo2(pThis, VERR_SUP_VP_BAD_SECTION_FILE_SIZE, "%s: Section %u: Invalid file size: %#x (cbMap=%#x, uSectRva=%#x)", pImage->pszName, i, cbFile, cbMap, uSectRva); /* Validate the protection and bits. */ if (!pImage->fApiSetSchemaOnlySection1 || i == 0) { uint32_t fProt; switch (pThis->aSecHdrs[i].Characteristics & (IMAGE_SCN_MEM_EXECUTE | IMAGE_SCN_MEM_READ | IMAGE_SCN_MEM_WRITE)) { case IMAGE_SCN_MEM_READ: fProt = PAGE_READONLY; break; case IMAGE_SCN_MEM_READ | IMAGE_SCN_MEM_WRITE: fProt = PAGE_READWRITE; if ( pThis->enmKind != SUPHARDNTVPKIND_VERIFY_ONLY && pThis->enmKind != SUPHARDNTVPKIND_CHILD_PURIFICATION && !suplibHardenedMemComp(pThis->aSecHdrs[i].Name, ".mrdata", 8)) /* w8.1, ntdll. Changed by proc init. */ fProt = PAGE_READONLY; break; case IMAGE_SCN_MEM_READ | IMAGE_SCN_MEM_EXECUTE: fProt = PAGE_EXECUTE_READ; break; case IMAGE_SCN_MEM_EXECUTE: fProt = PAGE_EXECUTE; break; case IMAGE_SCN_MEM_EXECUTE | IMAGE_SCN_MEM_READ | IMAGE_SCN_MEM_WRITE: /* Only the executable is allowed to have this section, and it's protected after we're done patching. */ if (!pImage->fDll) { if (pThis->enmKind == SUPHARDNTVPKIND_CHILD_PURIFICATION) fProt = PAGE_EXECUTE_READWRITE; else fProt = PAGE_EXECUTE_READ; break; } default: return supHardNtVpSetInfo2(pThis, VERR_SUP_VP_UNEXPECTED_SECTION_FLAGS, "%s: Section %u: Unexpected characteristics: %#x (uSectRva=%#x, cbMap=%#x)", pImage->pszName, i, pThis->aSecHdrs[i].Characteristics, uSectRva, cbMap); } /* The section bits. Child purification verifies all, normal verification verifies all except where the executable is concerned (due to opening vboxdrv during early process init). */ if ( ( (pThis->aSecHdrs[i].Characteristics & (IMAGE_SCN_MEM_EXECUTE | IMAGE_SCN_CNT_CODE)) && !(pThis->aSecHdrs[i].Characteristics & IMAGE_SCN_MEM_WRITE)) || (pThis->aSecHdrs[i].Characteristics & (IMAGE_SCN_MEM_READ | IMAGE_SCN_MEM_WRITE)) == IMAGE_SCN_MEM_READ || (pThis->enmKind == SUPHARDNTVPKIND_VERIFY_ONLY && pImage->fDll) || pThis->enmKind == SUPHARDNTVPKIND_CHILD_PURIFICATION) { rc = VINF_SUCCESS; if (uRva < uSectRva && !pImage->fApiSetSchemaOnlySection1) /* Any gap worth checking? */ rc = supHardNtVpFileMemCompareSection(pThis, pImage, uRva, uSectRva - uRva, pbBits + uRva, i - 1, NULL, 0, fPrevProt); if (RT_SUCCESS(rc)) rc = supHardNtVpFileMemCompareSection(pThis, pImage, uSectRva, cbMap, pbBits + uSectRva, i, aSkipAreas, cSkipAreas, fProt); if (RT_SUCCESS(rc)) { uint32_t cbMapAligned = i + 1 < cSections && !pImage->fApiSetSchemaOnlySection1 ? RT_ALIGN_32(cbMap, cbSectAlign) : RT_ALIGN_32(cbMap, PAGE_SIZE); if (cbMapAligned > cbMap) rc = supHardNtVpFileMemCompareSection(pThis, pImage, uSectRva + cbMap, cbMapAligned - cbMap, g_abRTZeroPage, i, NULL, 0, fProt); } if (RT_FAILURE(rc)) return rc; } /* The protection (must be checked afterwards!). */ rc = supHardNtVpCheckSectionProtection(pThis, pImage, uSectRva, RT_ALIGN_32(cbMap, PAGE_SIZE), fProt); if (RT_FAILURE(rc)) return rc; fPrevProt = fProt; } /* Advance the RVA. */ uRva = uSectRva + RT_ALIGN_32(cbMap, cbSectAlign); } return VINF_SUCCESS; } /** * Verifies the signature of the given image on disk, then checks if the memory * mapping matches what we verified. * * @returns VBox status code. * @param pThis The process scanning state structure (for the * two scratch buffers). * @param pImage The image data collected during the address * space scan. */ static int supHardNtVpVerifyImage(PSUPHNTVPSTATE pThis, PSUPHNTVPIMAGE pImage) { /* * Validate the file signature first, then do the memory compare. */ int rc; if ( pImage->pCacheEntry != NULL && pImage->pCacheEntry->hLdrMod != NIL_RTLDRMOD) { rc = supHardNtLdrCacheEntryVerify(pImage->pCacheEntry, pImage->Name.UniStr.Buffer, pThis->pErrInfo); if (RT_SUCCESS(rc)) rc = supHardNtVpVerifyImageMemoryCompare(pThis, pImage); } else rc = supHardNtVpSetInfo2(pThis, VERR_OPEN_FAILED, "pCacheEntry/hLdrMod is NIL! Impossible!"); return rc; } /** * Verifies that there is only one thread in the process. * * @returns VBox status code. * @param hProcess The process. * @param hThread The thread. * @param pErrInfo Pointer to error info structure. Optional. */ DECLHIDDEN(int) supHardNtVpThread(HANDLE hProcess, HANDLE hThread, PRTERRINFO pErrInfo) { RT_NOREF1(hProcess); /* * Use the ThreadAmILastThread request to check that there is only one * thread in the process. * Seems this isn't entirely reliable when hThread isn't the current thread? */ ULONG cbIgn = 0; ULONG fAmI = 0; NTSTATUS rcNt = NtQueryInformationThread(hThread, ThreadAmILastThread, &fAmI, sizeof(fAmI), &cbIgn); if (!NT_SUCCESS(rcNt)) return supHardNtVpSetInfo1(pErrInfo, VERR_SUP_VP_NT_QI_THREAD_ERROR, "NtQueryInformationThread/ThreadAmILastThread -> %#x", rcNt); if (!fAmI) return supHardNtVpSetInfo1(pErrInfo, VERR_SUP_VP_THREAD_NOT_ALONE, "More than one thread in process"); /** @todo Would be nice to verify the relationship between hProcess and hThread * as well... */ return VINF_SUCCESS; } /** * Verifies that there isn't a debugger attached to the process. * * @returns VBox status code. * @param hProcess The process. * @param pErrInfo Pointer to error info structure. Optional. */ DECLHIDDEN(int) supHardNtVpDebugger(HANDLE hProcess, PRTERRINFO pErrInfo) { #ifndef VBOX_WITHOUT_DEBUGGER_CHECKS /* * Use the ProcessDebugPort request to check there is no debugger * currently attached to the process. */ ULONG cbIgn = 0; uintptr_t uPtr = ~(uintptr_t)0; NTSTATUS rcNt = NtQueryInformationProcess(hProcess, ProcessDebugPort, &uPtr, sizeof(uPtr), &cbIgn); if (!NT_SUCCESS(rcNt)) return supHardNtVpSetInfo1(pErrInfo, VERR_SUP_VP_NT_QI_PROCESS_DBG_PORT_ERROR, "NtQueryInformationProcess/ProcessDebugPort -> %#x", rcNt); if (uPtr != 0) return supHardNtVpSetInfo1(pErrInfo, VERR_SUP_VP_DEBUGGED, "Debugger attached (%#zx)", uPtr); #else RT_NOREF2(hProcess, pErrInfo); #endif /* !VBOX_WITHOUT_DEBUGGER_CHECKS */ return VINF_SUCCESS; } /** * Matches two UNICODE_STRING structures in a case sensitive fashion. * * @returns true if equal, false if not. * @param pUniStr1 The first unicode string. * @param pUniStr2 The first unicode string. */ static bool supHardNtVpAreUniStringsEqual(PCUNICODE_STRING pUniStr1, PCUNICODE_STRING pUniStr2) { if (pUniStr1->Length != pUniStr2->Length) return false; return suplibHardenedMemComp(pUniStr1->Buffer, pUniStr2->Buffer, pUniStr1->Length) == 0; } /** * Performs a case insensitive comparison of an ASCII and an UTF-16 file name. * * @returns true / false * @param pszName1 The ASCII name. * @param pwszName2 The UTF-16 name. */ static bool supHardNtVpAreNamesEqual(const char *pszName1, PCRTUTF16 pwszName2) { for (;;) { char ch1 = *pszName1++; RTUTF16 wc2 = *pwszName2++; if (ch1 != wc2) { ch1 = RT_C_TO_LOWER(ch1); wc2 = wc2 < 0x80 ? RT_C_TO_LOWER(wc2) : wc2; if (ch1 != wc2) return false; } if (!ch1) return true; } } /** * Compares two paths, expanding 8.3 short names as needed. * * @returns true / false. * @param pUniStr1 The first path. Must be zero terminated! * @param pUniStr2 The second path. Must be zero terminated! */ static bool supHardNtVpArePathsEqual(PCUNICODE_STRING pUniStr1, PCUNICODE_STRING pUniStr2) { /* Both strings must be null terminated. */ Assert(pUniStr1->Buffer[pUniStr1->Length / sizeof(WCHAR)] == '\0'); Assert(pUniStr2->Buffer[pUniStr1->Length / sizeof(WCHAR)] == '\0'); /* Simple compare first.*/ if (supHardNtVpAreUniStringsEqual(pUniStr1, pUniStr2)) return true; /* Make long names if needed. */ UNICODE_STRING UniStrLong1 = { 0, 0, NULL }; if (RTNtPathFindPossible8dot3Name(pUniStr1->Buffer)) { int rc = RTNtPathExpand8dot3PathA(pUniStr1, false /*fPathOnly*/, &UniStrLong1); if (RT_SUCCESS(rc)) pUniStr1 = &UniStrLong1; } UNICODE_STRING UniStrLong2 = { 0, 0, NULL }; if (RTNtPathFindPossible8dot3Name(pUniStr2->Buffer)) { int rc = RTNtPathExpand8dot3PathA(pUniStr2, false /*fPathOnly*/, &UniStrLong2); if (RT_SUCCESS(rc)) pUniStr2 = &UniStrLong2; } /* Compare again. */ bool fCompare = supHardNtVpAreUniStringsEqual(pUniStr1, pUniStr2); /* Clean up. */ if (UniStrLong1.Buffer) RTUtf16Free(UniStrLong1.Buffer); if (UniStrLong2.Buffer) RTUtf16Free(UniStrLong2.Buffer); return fCompare; } /** * Records an additional memory region for an image. * * May trash pThis->abMemory. * * @returns VBox status code. * @retval VINF_OBJECT_DESTROYED if we've unmapped the image (child * purification only). * @param pThis The process scanning state structure. * @param pImage The new image structure. Only the unicode name * buffer is valid (it's zero-terminated). * @param pMemInfo The memory information for the image. */ static int supHardNtVpNewImage(PSUPHNTVPSTATE pThis, PSUPHNTVPIMAGE pImage, PMEMORY_BASIC_INFORMATION pMemInfo) { /* * If the filename or path contains short names, we have to get the long * path so that we will recognize the DLLs and their location. */ int rc83Exp = VERR_IGNORED; PUNICODE_STRING pLongName = &pImage->Name.UniStr; if (RTNtPathFindPossible8dot3Name(pLongName->Buffer)) { AssertCompile(sizeof(pThis->abMemory) > sizeof(pImage->Name)); PUNICODE_STRING pTmp = (PUNICODE_STRING)pThis->abMemory; pTmp->MaximumLength = (USHORT)RT_MIN(_64K - 1, sizeof(pThis->abMemory) - sizeof(*pTmp)) - sizeof(RTUTF16); pTmp->Length = pImage->Name.UniStr.Length; pTmp->Buffer = (PRTUTF16)(pTmp + 1); memcpy(pTmp->Buffer, pLongName->Buffer, pLongName->Length + sizeof(RTUTF16)); rc83Exp = RTNtPathExpand8dot3Path(pTmp, false /*fPathOnly*/); Assert(rc83Exp == VINF_SUCCESS); Assert(pTmp->Buffer[pTmp->Length / sizeof(RTUTF16)] == '\0'); if (rc83Exp == VINF_SUCCESS) SUP_DPRINTF(("supHardNtVpNewImage: 8dot3 -> long: '%ls' -> '%ls'\n", pLongName->Buffer, pTmp->Buffer)); else SUP_DPRINTF(("supHardNtVpNewImage: RTNtPathExpand8dot3Path returns %Rrc for '%ls' (-> '%ls')\n", rc83Exp, pLongName->Buffer, pTmp->Buffer)); pLongName = pTmp; } /* * Extract the final component. */ RTUTF16 wc; unsigned cwcDirName = pLongName->Length / sizeof(WCHAR); PCRTUTF16 pwszFilename = &pLongName->Buffer[cwcDirName]; while ( cwcDirName > 0 && (wc = pwszFilename[-1]) != '\\' && wc != '/' && wc != ':') { pwszFilename--; cwcDirName--; } if (!*pwszFilename) return supHardNtVpSetInfo2(pThis, VERR_SUP_VP_NO_IMAGE_MAPPING_NAME, "Empty filename (len=%u) for image at %p.", pLongName->Length, pMemInfo->BaseAddress); /* * Drop trailing slashes from the directory name. */ while ( cwcDirName > 0 && ( pLongName->Buffer[cwcDirName - 1] == '\\' || pLongName->Buffer[cwcDirName - 1] == '/')) cwcDirName--; /* * Match it against known DLLs. */ pImage->pszName = NULL; for (uint32_t i = 0; i < RT_ELEMENTS(g_apszSupNtVpAllowedDlls); i++) if (supHardNtVpAreNamesEqual(g_apszSupNtVpAllowedDlls[i], pwszFilename)) { pImage->pszName = g_apszSupNtVpAllowedDlls[i]; pImage->fDll = true; #ifndef VBOX_PERMIT_VISUAL_STUDIO_PROFILING /* The directory name must match the one we've got for System32. */ if ( ( cwcDirName * sizeof(WCHAR) != g_System32NtPath.UniStr.Length || suplibHardenedMemComp(pLongName->Buffer, g_System32NtPath.UniStr.Buffer, cwcDirName * sizeof(WCHAR)) ) # ifdef VBOX_PERMIT_MORE && ( pImage->pszName[0] != 'a' || pImage->pszName[1] != 'c' || !supHardViIsAppPatchDir(pLongName->Buffer, pLongName->Length / sizeof(WCHAR)) ) # endif ) return supHardNtVpSetInfo2(pThis, VERR_SUP_VP_NON_SYSTEM32_DLL, "Expected %ls to be loaded from %ls.", pLongName->Buffer, g_System32NtPath.UniStr.Buffer); # ifdef VBOX_PERMIT_MORE if (g_uNtVerCombined < SUP_NT_VER_W70 && i >= VBOX_PERMIT_MORE_FIRST_IDX) pImage->pszName = NULL; /* hard limit: user32.dll is unwanted prior to w7. */ # endif #endif /* VBOX_PERMIT_VISUAL_STUDIO_PROFILING */ break; } if (!pImage->pszName) { /* * Not a known DLL, is it a known executable? */ for (uint32_t i = 0; i < RT_ELEMENTS(g_apszSupNtVpAllowedVmExes); i++) if (supHardNtVpAreNamesEqual(g_apszSupNtVpAllowedVmExes[i], pwszFilename)) { pImage->pszName = g_apszSupNtVpAllowedVmExes[i]; pImage->fDll = false; break; } } if (!pImage->pszName) { /* * Unknown image. * * If we're cleaning up a child process, we can unmap the offending * DLL... Might have interesting side effects, or at least interesting * as in "may you live in interesting times". */ #ifdef IN_RING3 if ( pMemInfo->AllocationBase == pMemInfo->BaseAddress && pThis->enmKind == SUPHARDNTVPKIND_CHILD_PURIFICATION) { SUP_DPRINTF(("supHardNtVpScanVirtualMemory: Unmapping image mem at %p (%p LB %#zx) - '%ls'\n", pMemInfo->AllocationBase, pMemInfo->BaseAddress, pMemInfo->RegionSize, pwszFilename)); NTSTATUS rcNt = NtUnmapViewOfSection(pThis->hProcess, pMemInfo->AllocationBase); if (NT_SUCCESS(rcNt)) return VINF_OBJECT_DESTROYED; pThis->cFixes++; SUP_DPRINTF(("supHardNtVpScanVirtualMemory: NtUnmapViewOfSection(,%p) failed: %#x\n", pMemInfo->AllocationBase, rcNt)); } else if (pThis->enmKind == SUPHARDNTVPKIND_SELF_PURIFICATION_LIMITED) { SUP_DPRINTF(("supHardNtVpScanVirtualMemory: Ignoring unknown mem at %p LB %#zx (base %p) - '%ls'\n", pMemInfo->BaseAddress, pMemInfo->RegionSize, pMemInfo->AllocationBase, pwszFilename)); return VINF_OBJECT_DESTROYED; } #endif /* * Special error message if we can. */ if ( pMemInfo->AllocationBase == pMemInfo->BaseAddress && ( supHardNtVpAreNamesEqual("sysfer.dll", pwszFilename) || supHardNtVpAreNamesEqual("sysfer32.dll", pwszFilename) || supHardNtVpAreNamesEqual("sysfer64.dll", pwszFilename) || supHardNtVpAreNamesEqual("sysfrethunk.dll", pwszFilename)) ) { supHardNtVpSetInfo2(pThis, VERR_SUP_VP_SYSFER_DLL, "Found %ls at %p - This is probably part of Symantec Endpoint Protection. \n" "You or your admin need to add and exception to the Application and Device Control (ADC) " "component (or disable it) to prevent ADC from injecting itself into the VirtualBox VM processes. " "See http://www.symantec.com/connect/articles/creating-application-control-exclusions-symantec-endpoint-protection-121" , pLongName->Buffer, pMemInfo->BaseAddress); return pThis->rcResult = VERR_SUP_VP_SYSFER_DLL; /* Try make sure this is what the user sees first! */ } return supHardNtVpSetInfo2(pThis, VERR_SUP_VP_NOT_KNOWN_DLL_OR_EXE, "Unknown image file %ls at %p. (rc83Exp=%Rrc)", pLongName->Buffer, pMemInfo->BaseAddress, rc83Exp); } /* * Checks for multiple mappings of the same DLL but with different image file paths. */ uint32_t i = pThis->cImages; while (i-- > 1) if (pImage->pszName == pThis->aImages[i].pszName) return supHardNtVpSetInfo2(pThis, VERR_SUP_VP_DUPLICATE_DLL_MAPPING, "Duplicate image entries for %s: %ls and %ls", pImage->pszName, pImage->Name.UniStr.Buffer, pThis->aImages[i].Name.UniStr.Buffer); /* * Since it's a new image, we expect to be at the start of the mapping now. */ if (pMemInfo->AllocationBase != pMemInfo->BaseAddress) return supHardNtVpSetInfo2(pThis, VERR_SUP_VP_IMAGE_MAPPING_BASE_ERROR, "Invalid AllocationBase/BaseAddress for %s: %p vs %p.", pImage->pszName, pMemInfo->AllocationBase, pMemInfo->BaseAddress); /* * Check for size/rva overflow. */ if (pMemInfo->RegionSize >= _2G) return supHardNtVpSetInfo2(pThis, VERR_SUP_VP_TOO_LARGE_REGION, "Region 0 of image %s is too large: %p.", pImage->pszName, pMemInfo->RegionSize); /* * Fill in details from the memory info. */ pImage->uImageBase = (uintptr_t)pMemInfo->AllocationBase; pImage->cbImage = pMemInfo->RegionSize; pImage->pCacheEntry= NULL; pImage->cRegions = 1; pImage->aRegions[0].uRva = 0; pImage->aRegions[0].cb = (uint32_t)pMemInfo->RegionSize; pImage->aRegions[0].fProt = pMemInfo->Protect; if (suplibHardenedStrCmp(pImage->pszName, "ntdll.dll") == 0) pImage->fNtCreateSectionPatch = true; else if (suplibHardenedStrCmp(pImage->pszName, "apisetschema.dll") == 0) pImage->fApiSetSchemaOnlySection1 = true; /** @todo Check the ApiSetMap field in the PEB. */ #ifdef VBOX_PERMIT_MORE else if (suplibHardenedStrCmp(pImage->pszName, "acres.dll") == 0) pImage->f32bitResourceDll = true; #endif return VINF_SUCCESS; } /** * Records an additional memory region for an image. * * @returns VBox status code. * @param pThis The process scanning state structure. * @param pImage The image. * @param pMemInfo The memory information for the region. */ static int supHardNtVpAddRegion(PSUPHNTVPSTATE pThis, PSUPHNTVPIMAGE pImage, PMEMORY_BASIC_INFORMATION pMemInfo) { /* * Make sure the base address matches. */ if (pImage->uImageBase != (uintptr_t)pMemInfo->AllocationBase) return supHardNtVpSetInfo2(pThis, VERR_SUPLIB_NT_PROCESS_UNTRUSTED_3, "Base address mismatch for %s: have %p, found %p for region %p LB %#zx.", pImage->pszName, pImage->uImageBase, pMemInfo->AllocationBase, pMemInfo->BaseAddress, pMemInfo->RegionSize); /* * Check for size and rva overflows. */ uintptr_t uRva = (uintptr_t)pMemInfo->BaseAddress - pImage->uImageBase; if (pMemInfo->RegionSize >= _2G) return supHardNtVpSetInfo2(pThis, VERR_SUP_VP_TOO_LARGE_REGION, "Region %u of image %s is too large: %p/%p.", pImage->pszName, pMemInfo->RegionSize, uRva); if (uRva >= _2G) return supHardNtVpSetInfo2(pThis, VERR_SUP_VP_TOO_HIGH_REGION_RVA, "Region %u of image %s is too high: %p/%p.", pImage->pszName, pMemInfo->RegionSize, uRva); /* * Record the region. */ uint32_t iRegion = pImage->cRegions; if (iRegion + 1 >= RT_ELEMENTS(pImage->aRegions)) return supHardNtVpSetInfo2(pThis, VERR_SUP_VP_TOO_MANY_IMAGE_REGIONS, "Too many regions for %s.", pImage->pszName); pImage->aRegions[iRegion].uRva = (uint32_t)uRva; pImage->aRegions[iRegion].cb = (uint32_t)pMemInfo->RegionSize; pImage->aRegions[iRegion].fProt = pMemInfo->Protect; pImage->cbImage = pImage->aRegions[iRegion].uRva + pImage->aRegions[iRegion].cb; pImage->cRegions++; pImage->fApiSetSchemaOnlySection1 = false; return VINF_SUCCESS; } #ifdef IN_RING3 /** * Frees (or replaces) executable memory of allocation type private. * * @returns True if nothing really bad happen, false if to quit ASAP because we * killed the process being scanned. * @param pThis The process scanning state structure. Details * about images are added to this. * @param hProcess The process to verify. * @param pMemInfo The information we've got on this private * executable memory. */ static bool supHardNtVpFreeOrReplacePrivateExecMemory(PSUPHNTVPSTATE pThis, HANDLE hProcess, MEMORY_BASIC_INFORMATION const *pMemInfo) { NTSTATUS rcNt; /* * Try figure the entire allocation size. Free/Alloc may fail otherwise. */ PVOID pvFree = pMemInfo->AllocationBase; SIZE_T cbFree = pMemInfo->RegionSize + ((uintptr_t)pMemInfo->BaseAddress - (uintptr_t)pMemInfo->AllocationBase); for (;;) { SIZE_T cbActual = 0; MEMORY_BASIC_INFORMATION MemInfo2 = { 0, 0, 0, 0, 0, 0, 0 }; uintptr_t uPtrNext = (uintptr_t)pvFree + cbFree; rcNt = g_pfnNtQueryVirtualMemory(hProcess, (void const *)uPtrNext, MemoryBasicInformation, &MemInfo2, sizeof(MemInfo2), &cbActual); if (!NT_SUCCESS(rcNt)) break; if (pMemInfo->AllocationBase != MemInfo2.AllocationBase) break; if (MemInfo2.RegionSize == 0) break; cbFree += MemInfo2.RegionSize; } SUP_DPRINTF(("supHardNtVpFreeOrReplacePrivateExecMemory: %s exec mem at %p (LB %#zx, %p LB %#zx)\n", pThis->fFlags & SUPHARDNTVP_F_EXEC_ALLOC_REPLACE_WITH_RW ? "Replacing" : "Freeing", pvFree, cbFree, pMemInfo->BaseAddress, pMemInfo->RegionSize)); /* * In the BSOD workaround mode, we need to make a copy of the memory before * freeing it. Bird abuses this code for logging purposes too. */ uintptr_t uCopySrc = (uintptr_t)pvFree; size_t cbCopy = 0; void *pvCopy = NULL; //if (pThis->fFlags & SUPHARDNTVP_F_EXEC_ALLOC_REPLACE_WITH_RW) { cbCopy = cbFree; pvCopy = RTMemAllocZ(cbCopy); if (!pvCopy) { supHardNtVpSetInfo2(pThis, VERR_SUP_VP_REPLACE_VIRTUAL_MEMORY_FAILED, "RTMemAllocZ(%#zx) failed", cbCopy); return true; } rcNt = supHardNtVpReadMem(hProcess, uCopySrc, pvCopy, cbCopy); if (!NT_SUCCESS(rcNt)) supHardNtVpSetInfo2(pThis, VERR_SUP_VP_REPLACE_VIRTUAL_MEMORY_FAILED, "Error reading data from original alloc: %#x (%p LB %#zx)", rcNt, uCopySrc, cbCopy, rcNt); for (size_t off = 0; off < cbCopy; off += 256) { size_t const cbChunk = RT_MIN(256, cbCopy - off); void const *pvChunk = (uint8_t const *)pvCopy + off; if (!ASMMemIsZero(pvChunk, cbChunk)) SUP_DPRINTF(("%.*RhxD\n", cbChunk, pvChunk)); } if (pThis->fFlags & SUPHARDNTVP_F_EXEC_ALLOC_REPLACE_WITH_RW) supR3HardenedLogFlush(); } /* * Free the memory. */ for (uint32_t i = 0; i < 10; i++) { PVOID pvFreeInOut = pvFree; SIZE_T cbFreeInOut = 0; rcNt = NtFreeVirtualMemory(hProcess, &pvFreeInOut, &cbFreeInOut, MEM_RELEASE); if (NT_SUCCESS(rcNt)) { SUP_DPRINTF(("supHardNtVpFreeOrReplacePrivateExecMemory: Free attempt #1 succeeded: %#x [%p/%p LB 0/%#zx]\n", rcNt, pvFree, pvFreeInOut, cbFreeInOut)); supR3HardenedLogFlush(); } else { SUP_DPRINTF(("supHardNtVpFreeOrReplacePrivateExecMemory: Free attempt #1 failed: %#x [%p LB 0]\n", rcNt, pvFree)); supR3HardenedLogFlush(); pvFreeInOut = pvFree; cbFreeInOut = cbFree; rcNt = NtFreeVirtualMemory(hProcess, &pvFreeInOut, &cbFreeInOut, MEM_RELEASE); if (NT_SUCCESS(rcNt)) { SUP_DPRINTF(("supHardNtVpFreeOrReplacePrivateExecMemory: Free attempt #2 succeeded: %#x [%p/%p LB %#zx/%#zx]\n", rcNt, pvFree, pvFreeInOut, cbFree, cbFreeInOut)); supR3HardenedLogFlush(); } else { SUP_DPRINTF(("supHardNtVpFreeOrReplacePrivateExecMemory: Free attempt #2 failed: %#x [%p LB %#zx]\n", rcNt, pvFree, cbFree)); supR3HardenedLogFlush(); pvFreeInOut = pMemInfo->BaseAddress; cbFreeInOut = pMemInfo->RegionSize; rcNt = NtFreeVirtualMemory(hProcess, &pvFreeInOut, &cbFreeInOut, MEM_RELEASE); if (NT_SUCCESS(rcNt)) { pvFree = pMemInfo->BaseAddress; cbFree = pMemInfo->RegionSize; SUP_DPRINTF(("supHardNtVpFreeOrReplacePrivateExecMemory: Free attempt #3 succeeded [%p LB %#zx]\n", pvFree, cbFree)); supR3HardenedLogFlush(); } else supHardNtVpSetInfo2(pThis, VERR_SUP_VP_FREE_VIRTUAL_MEMORY_FAILED, "NtFreeVirtualMemory [%p LB %#zx and %p LB %#zx] failed: %#x", pvFree, cbFree, pMemInfo->BaseAddress, pMemInfo->RegionSize, rcNt); } } /* * Query the region again, redo the free operation if there's still memory there. */ if (!NT_SUCCESS(rcNt)) break; SIZE_T cbActual = 0; MEMORY_BASIC_INFORMATION MemInfo3 = { 0, 0, 0, 0, 0, 0, 0 }; NTSTATUS rcNt2 = g_pfnNtQueryVirtualMemory(hProcess, pvFree, MemoryBasicInformation, &MemInfo3, sizeof(MemInfo3), &cbActual); if (!NT_SUCCESS(rcNt2)) break; SUP_DPRINTF(("supHardNtVpFreeOrReplacePrivateExecMemory: QVM after free %u: [%p]/%p LB %#zx s=%#x ap=%#x rp=%#p\n", i, MemInfo3.AllocationBase, MemInfo3.BaseAddress, MemInfo3.RegionSize, MemInfo3.State, MemInfo3.AllocationProtect, MemInfo3.Protect)); supR3HardenedLogFlush(); if (MemInfo3.State == MEM_FREE || !(pThis->fFlags & SUPHARDNTVP_F_EXEC_ALLOC_REPLACE_WITH_RW)) break; NtYieldExecution(); SUP_DPRINTF(("supHardNtVpFreeOrReplacePrivateExecMemory: Retrying free...\n")); supR3HardenedLogFlush(); } /* * Restore memory as non-executable - Kludge for Trend Micro sakfile.sys * and Digital Guardian dgmaster.sys BSODs. */ if (NT_SUCCESS(rcNt) && (pThis->fFlags & SUPHARDNTVP_F_EXEC_ALLOC_REPLACE_WITH_RW)) { PVOID pvAlloc = pvFree; SIZE_T cbAlloc = cbFree; rcNt = NtAllocateVirtualMemory(hProcess, &pvAlloc, 0, &cbAlloc, MEM_COMMIT, PAGE_READWRITE); if (!NT_SUCCESS(rcNt)) { supHardNtVpSetInfo2(pThis, VERR_SUP_VP_REPLACE_VIRTUAL_MEMORY_FAILED, "NtAllocateVirtualMemory (%p LB %#zx) failed with rcNt=%#x allocating " "replacement memory for working around buggy protection software. " "See VBoxStartup.log for more details", pvAlloc, cbFree, rcNt); supR3HardenedLogFlush(); NtTerminateProcess(hProcess, VERR_SUP_VP_REPLACE_VIRTUAL_MEMORY_FAILED); return false; } if ( (uintptr_t)pvFree < (uintptr_t)pvAlloc || (uintptr_t)pvFree + cbFree > (uintptr_t)pvAlloc + cbFree) { supHardNtVpSetInfo2(pThis, VERR_SUP_VP_REPLACE_VIRTUAL_MEMORY_FAILED, "We wanted NtAllocateVirtualMemory to get us %p LB %#zx, but it returned %p LB %#zx.", pMemInfo->BaseAddress, pMemInfo->RegionSize, pvFree, cbFree, rcNt); supR3HardenedLogFlush(); NtTerminateProcess(hProcess, VERR_SUP_VP_REPLACE_VIRTUAL_MEMORY_FAILED); return false; } /* * Copy what we can, considering the 2nd free attempt. */ uint8_t *pbDst = (uint8_t *)pvFree; size_t cbDst = cbFree; uint8_t *pbSrc = (uint8_t *)pvCopy; size_t cbSrc = cbCopy; if ((uintptr_t)pbDst != uCopySrc) { if ((uintptr_t)pbDst > uCopySrc) { uintptr_t cbAdj = (uintptr_t)pbDst - uCopySrc; pbSrc += cbAdj; cbSrc -= cbAdj; } else { uintptr_t cbAdj = uCopySrc - (uintptr_t)pbDst; pbDst += cbAdj; cbDst -= cbAdj; } } if (cbSrc > cbDst) cbSrc = cbDst; SIZE_T cbWritten; rcNt = NtWriteVirtualMemory(hProcess, pbDst, pbSrc, cbSrc, &cbWritten); if (NT_SUCCESS(rcNt)) { SUP_DPRINTF(("supHardNtVpFreeOrReplacePrivateExecMemory: Restored the exec memory as non-exec.\n")); supR3HardenedLogFlush(); } else { supHardNtVpSetInfo2(pThis, VERR_SUP_VP_FREE_VIRTUAL_MEMORY_FAILED, "NtWriteVirtualMemory (%p LB %#zx) failed: %#x", pMemInfo->BaseAddress, pMemInfo->RegionSize, rcNt); supR3HardenedLogFlush(); NtTerminateProcess(hProcess, VERR_SUP_VP_REPLACE_VIRTUAL_MEMORY_FAILED); return false; } } if (pvCopy) RTMemFree(pvCopy); return true; } #endif /* IN_RING3 */ /** * Scans the virtual memory of the process. * * This collects the locations of DLLs and the EXE, and verifies that executable * memory is only associated with these. May trash pThis->abMemory. * * @returns VBox status code. * @param pThis The process scanning state structure. Details * about images are added to this. * @param hProcess The process to verify. */ static int supHardNtVpScanVirtualMemory(PSUPHNTVPSTATE pThis, HANDLE hProcess) { SUP_DPRINTF(("supHardNtVpScanVirtualMemory: enmKind=%s\n", pThis->enmKind == SUPHARDNTVPKIND_VERIFY_ONLY ? "VERIFY_ONLY" : pThis->enmKind == SUPHARDNTVPKIND_CHILD_PURIFICATION ? "CHILD_PURIFICATION" : "SELF_PURIFICATION")); uint32_t cXpExceptions = 0; uintptr_t cbAdvance = 0; uintptr_t uPtrWhere = 0; #ifdef VBOX_PERMIT_VERIFIER_DLL for (uint32_t i = 0; i < 10240; i++) #else for (uint32_t i = 0; i < 1024; i++) #endif { SIZE_T cbActual = 0; MEMORY_BASIC_INFORMATION MemInfo = { 0, 0, 0, 0, 0, 0, 0 }; NTSTATUS rcNt = g_pfnNtQueryVirtualMemory(hProcess, (void const *)uPtrWhere, MemoryBasicInformation, &MemInfo, sizeof(MemInfo), &cbActual); if (!NT_SUCCESS(rcNt)) { if (rcNt == STATUS_INVALID_PARAMETER) return pThis->rcResult; return supHardNtVpSetInfo2(pThis, VERR_SUP_VP_NT_QI_VIRTUAL_MEMORY_ERROR, "NtQueryVirtualMemory failed for %p: %#x", uPtrWhere, rcNt); } /* * Record images. */ if ( MemInfo.Type == SEC_IMAGE || MemInfo.Type == SEC_PROTECTED_IMAGE || MemInfo.Type == (SEC_IMAGE | SEC_PROTECTED_IMAGE)) { uint32_t iImg = pThis->cImages; rcNt = g_pfnNtQueryVirtualMemory(hProcess, (void const *)uPtrWhere, MemorySectionName, &pThis->aImages[iImg].Name, sizeof(pThis->aImages[iImg].Name) - sizeof(WCHAR), &cbActual); if (!NT_SUCCESS(rcNt)) return supHardNtVpSetInfo2(pThis, VERR_SUP_VP_NT_QI_VIRTUAL_MEMORY_NM_ERROR, "NtQueryVirtualMemory/MemorySectionName failed for %p: %#x", uPtrWhere, rcNt); pThis->aImages[iImg].Name.UniStr.Buffer[pThis->aImages[iImg].Name.UniStr.Length / sizeof(WCHAR)] = '\0'; SUP_DPRINTF((MemInfo.AllocationBase == MemInfo.BaseAddress ? " *%p-%p %#06x/%#06x %#09x %ls\n" : " %p-%p %#06x/%#06x %#09x %ls\n", MemInfo.BaseAddress, (uintptr_t)MemInfo.BaseAddress + MemInfo.RegionSize - 1, MemInfo.Protect, MemInfo.AllocationProtect, MemInfo.Type, pThis->aImages[iImg].Name.UniStr.Buffer)); /* New or existing image? */ bool fNew = true; uint32_t iSearch = iImg; while (iSearch-- > 0) if (supHardNtVpAreUniStringsEqual(&pThis->aImages[iSearch].Name.UniStr, &pThis->aImages[iImg].Name.UniStr)) { int rc = supHardNtVpAddRegion(pThis, &pThis->aImages[iSearch], &MemInfo); if (RT_FAILURE(rc)) return rc; fNew = false; break; } else if (pThis->aImages[iSearch].uImageBase == (uintptr_t)MemInfo.AllocationBase) return supHardNtVpSetInfo2(pThis, VERR_SUP_VP_NT_MAPPING_NAME_CHANGED, "Unexpected base address match"); if (fNew) { int rc = supHardNtVpNewImage(pThis, &pThis->aImages[iImg], &MemInfo); if (RT_SUCCESS(rc)) { if (rc != VINF_OBJECT_DESTROYED) { pThis->cImages++; if (pThis->cImages >= RT_ELEMENTS(pThis->aImages)) return supHardNtVpSetInfo2(pThis, VERR_SUP_VP_TOO_MANY_DLLS_LOADED, "Internal error: aImages is full.\n"); } } #ifdef IN_RING3 /* Continue and add more information if unknown DLLs are found. */ else if (rc != VERR_SUP_VP_NOT_KNOWN_DLL_OR_EXE && rc != VERR_SUP_VP_NON_SYSTEM32_DLL) return rc; #else else return rc; #endif } } /* * XP, W2K3: Ignore the CSRSS read-only region as best we can. */ else if ( (MemInfo.Protect & (PAGE_EXECUTE | PAGE_EXECUTE_READ | PAGE_EXECUTE_READWRITE | PAGE_EXECUTE_WRITECOPY)) == PAGE_EXECUTE_READ && cXpExceptions == 0 && (uintptr_t)MemInfo.BaseAddress >= UINT32_C(0x78000000) /* && MemInfo.BaseAddress == pPeb->ReadOnlySharedMemoryBase */ && g_uNtVerCombined < SUP_MAKE_NT_VER_SIMPLE(6, 0) ) { cXpExceptions++; SUP_DPRINTF((" %p-%p %#06x/%#06x %#09x XP CSRSS read-only region\n", MemInfo.BaseAddress, (uintptr_t)MemInfo.BaseAddress + MemInfo.RegionSize - 1, MemInfo.Protect, MemInfo.AllocationProtect, MemInfo.Type)); } /* * Executable memory? */ #ifndef VBOX_PERMIT_VISUAL_STUDIO_PROFILING else if (MemInfo.Protect & (PAGE_EXECUTE | PAGE_EXECUTE_READ | PAGE_EXECUTE_READWRITE | PAGE_EXECUTE_WRITECOPY)) { SUP_DPRINTF((MemInfo.AllocationBase == MemInfo.BaseAddress ? " *%p-%p %#06x/%#06x %#09x !!\n" : " %p-%p %#06x/%#06x %#09x !!\n", MemInfo.BaseAddress, (uintptr_t)MemInfo.BaseAddress + MemInfo.RegionSize - 1, MemInfo.Protect, MemInfo.AllocationProtect, MemInfo.Type)); # ifdef IN_RING3 if (pThis->enmKind == SUPHARDNTVPKIND_CHILD_PURIFICATION) { /* * Free any private executable memory (sysplant.sys allocates executable memory). */ if (MemInfo.Type == MEM_PRIVATE) { if (!supHardNtVpFreeOrReplacePrivateExecMemory(pThis, hProcess, &MemInfo)) break; } /* * Unmap mapped memory, failing that, drop exec privileges. */ else if (MemInfo.Type == MEM_MAPPED) { SUP_DPRINTF(("supHardNtVpScanVirtualMemory: Unmapping exec mem at %p (%p/%p LB %#zx)\n", uPtrWhere, MemInfo.AllocationBase, MemInfo.BaseAddress, MemInfo.RegionSize)); rcNt = NtUnmapViewOfSection(hProcess, MemInfo.AllocationBase); if (!NT_SUCCESS(rcNt)) { PVOID pvCopy = MemInfo.BaseAddress; SIZE_T cbCopy = MemInfo.RegionSize; NTSTATUS rcNt2 = NtProtectVirtualMemory(hProcess, &pvCopy, &cbCopy, PAGE_NOACCESS, NULL); if (!NT_SUCCESS(rcNt2)) rcNt2 = NtProtectVirtualMemory(hProcess, &pvCopy, &cbCopy, PAGE_READONLY, NULL); if (!NT_SUCCESS(rcNt2)) supHardNtVpSetInfo2(pThis, VERR_SUP_VP_UNMAP_AND_PROTECT_FAILED, "NtUnmapViewOfSection (%p/%p LB %#zx) failed: %#x (%#x)", MemInfo.AllocationBase, MemInfo.BaseAddress, MemInfo.RegionSize, rcNt, rcNt2); } } else supHardNtVpSetInfo2(pThis, VERR_SUP_VP_UNKOWN_MEM_TYPE, "Unknown executable memory type %#x at %p/%p LB %#zx", MemInfo.Type, MemInfo.AllocationBase, MemInfo.BaseAddress, MemInfo.RegionSize); pThis->cFixes++; } else if (pThis->enmKind != SUPHARDNTVPKIND_SELF_PURIFICATION_LIMITED) # endif /* IN_RING3 */ supHardNtVpSetInfo2(pThis, VERR_SUP_VP_FOUND_EXEC_MEMORY, "Found executable memory at %p (%p LB %#zx): type=%#x prot=%#x state=%#x aprot=%#x abase=%p", uPtrWhere, MemInfo.BaseAddress, MemInfo.RegionSize, MemInfo.Type, MemInfo.Protect, MemInfo.State, MemInfo.AllocationBase, MemInfo.AllocationProtect); # ifndef IN_RING3 if (RT_FAILURE(pThis->rcResult)) return pThis->rcResult; # endif /* Continue add more information about the problematic process. */ } #endif /* VBOX_PERMIT_VISUAL_STUDIO_PROFILING */ else SUP_DPRINTF((MemInfo.AllocationBase == MemInfo.BaseAddress ? " *%p-%p %#06x/%#06x %#09x\n" : " %p-%p %#06x/%#06x %#09x\n", MemInfo.BaseAddress, (uintptr_t)MemInfo.BaseAddress + MemInfo.RegionSize - 1, MemInfo.Protect, MemInfo.AllocationProtect, MemInfo.Type)); /* * Advance. */ cbAdvance = MemInfo.RegionSize; if (uPtrWhere + cbAdvance <= uPtrWhere) return supHardNtVpSetInfo2(pThis, VERR_SUP_VP_EMPTY_REGION_TOO_LARGE, "Empty region at %p.", uPtrWhere); uPtrWhere += MemInfo.RegionSize; } return supHardNtVpSetInfo2(pThis, VERR_SUP_VP_TOO_MANY_MEMORY_REGIONS, "Too many virtual memory regions.\n"); } /** * Verifies the loader image, i.e. check cryptographic signatures if present. * * @returns VBox status code. * @param pEntry The loader cache entry. * @param pwszName The filename to use in error messages. * @param pErrInfo Where to return extened error information. */ DECLHIDDEN(int) supHardNtLdrCacheEntryVerify(PSUPHNTLDRCACHEENTRY pEntry, PCRTUTF16 pwszName, PRTERRINFO pErrInfo) { int rc = VINF_SUCCESS; if (!pEntry->fVerified) { rc = supHardenedWinVerifyImageByLdrMod(pEntry->hLdrMod, pwszName, pEntry->pNtViRdr, false /*fAvoidWinVerifyTrust*/, NULL /*pfWinVerifyTrust*/, pErrInfo); pEntry->fVerified = RT_SUCCESS(rc); } return rc; } /** * Allocates a image bits buffer and calls RTLdrGetBits on them. * * An assumption here is that there won't ever be concurrent use of the cache. * It's currently 104% single threaded, non-reentrant. Thus, we can't reuse the * pbBits allocation. * * @returns VBox status code * @param pEntry The loader cache entry. * @param ppbBits Where to return the pointer to the allocation. * @param uBaseAddress The image base address, see RTLdrGetBits. * @param pfnGetImport Import getter, see RTLdrGetBits. * @param pvUser The user argument for @a pfnGetImport. * @param pErrInfo Where to return extened error information. */ DECLHIDDEN(int) supHardNtLdrCacheEntryGetBits(PSUPHNTLDRCACHEENTRY pEntry, uint8_t **ppbBits, RTLDRADDR uBaseAddress, PFNRTLDRIMPORT pfnGetImport, void *pvUser, PRTERRINFO pErrInfo) { int rc; /* * First time around we have to allocate memory before we can get the image bits. */ if (!pEntry->pbBits) { size_t cbBits = RTLdrSize(pEntry->hLdrMod); if (cbBits >= _1M*32U) return supHardNtVpSetInfo1(pErrInfo, VERR_SUP_VP_IMAGE_TOO_BIG, "Image %s is too large: %zu bytes (%#zx).", pEntry->pszName, cbBits, cbBits); pEntry->pbBits = (uint8_t *)RTMemAllocZ(cbBits); if (!pEntry->pbBits) return supHardNtVpSetInfo1(pErrInfo, VERR_SUP_VP_NO_MEMORY, "Failed to allocate %zu bytes for image %s.", cbBits, pEntry->pszName); pEntry->fValidBits = false; /* paranoia */ rc = RTLdrGetBits(pEntry->hLdrMod, pEntry->pbBits, uBaseAddress, pfnGetImport, pvUser); if (RT_FAILURE(rc)) return supHardNtVpSetInfo1(pErrInfo, VERR_SUP_VP_NO_MEMORY, "RTLdrGetBits failed on image %s: %Rrc", pEntry->pszName, rc); pEntry->uImageBase = uBaseAddress; pEntry->fValidBits = pfnGetImport == NULL; } /* * Cache hit? No? * * Note! We cannot currently cache image bits for images with imports as we * don't control the way they're resolved. Fortunately, NTDLL and * the VM process images all have no imports. */ else if ( !pEntry->fValidBits || pEntry->uImageBase != uBaseAddress || pfnGetImport) { pEntry->fValidBits = false; rc = RTLdrGetBits(pEntry->hLdrMod, pEntry->pbBits, uBaseAddress, pfnGetImport, pvUser); if (RT_FAILURE(rc)) return supHardNtVpSetInfo1(pErrInfo, VERR_SUP_VP_NO_MEMORY, "RTLdrGetBits failed on image %s: %Rrc", pEntry->pszName, rc); pEntry->uImageBase = uBaseAddress; pEntry->fValidBits = pfnGetImport == NULL; } *ppbBits = pEntry->pbBits; return VINF_SUCCESS; } /** * Frees all resources associated with a cache entry and wipes the members * clean. * * @param pEntry The entry to delete. */ static void supHardNTLdrCacheDeleteEntry(PSUPHNTLDRCACHEENTRY pEntry) { if (pEntry->pbBits) { RTMemFree(pEntry->pbBits); pEntry->pbBits = NULL; } if (pEntry->hLdrMod != NIL_RTLDRMOD) { RTLdrClose(pEntry->hLdrMod); pEntry->hLdrMod = NIL_RTLDRMOD; pEntry->pNtViRdr = NULL; } else if (pEntry->pNtViRdr) { pEntry->pNtViRdr->Core.pfnDestroy(&pEntry->pNtViRdr->Core); pEntry->pNtViRdr = NULL; } if (pEntry->hFile) { NtClose(pEntry->hFile); pEntry->hFile = NULL; } pEntry->pszName = NULL; pEntry->fVerified = false; pEntry->fValidBits = false; pEntry->uImageBase = 0; } #ifdef IN_RING3 /** * Flushes the cache. * * This is called from one of two points in the hardened main code, first is * after respawning and the second is when we open the vboxdrv device for * unrestricted access. */ DECLHIDDEN(void) supR3HardenedWinFlushLoaderCache(void) { uint32_t i = g_cSupNtVpLdrCacheEntries; while (i-- > 0) supHardNTLdrCacheDeleteEntry(&g_aSupNtVpLdrCacheEntries[i]); g_cSupNtVpLdrCacheEntries = 0; } /** * Searches the cache for a loader image. * * @returns Pointer to the cache entry if found, NULL if not. * @param pszName The name (from g_apszSupNtVpAllowedVmExes or * g_apszSupNtVpAllowedDlls). */ static PSUPHNTLDRCACHEENTRY supHardNtLdrCacheLookupEntry(const char *pszName) { /* * Since the caller is supplying us a pszName from one of the two tables, * we can dispense with string compare and simply compare string pointers. */ uint32_t i = g_cSupNtVpLdrCacheEntries; while (i-- > 0) if (g_aSupNtVpLdrCacheEntries[i].pszName == pszName) return &g_aSupNtVpLdrCacheEntries[i]; return NULL; } #endif /* IN_RING3 */ static int supHardNtLdrCacheNewEntry(PSUPHNTLDRCACHEENTRY pEntry, const char *pszName, PUNICODE_STRING pUniStrPath, bool fDll, bool f32bitResourceDll, PRTERRINFO pErrInfo) { /* * Open the image file. */ HANDLE hFile = RTNT_INVALID_HANDLE_VALUE; IO_STATUS_BLOCK Ios = RTNT_IO_STATUS_BLOCK_INITIALIZER; OBJECT_ATTRIBUTES ObjAttr; InitializeObjectAttributes(&ObjAttr, pUniStrPath, OBJ_CASE_INSENSITIVE, NULL /*hRootDir*/, NULL /*pSecDesc*/); #ifdef IN_RING0 ObjAttr.Attributes |= OBJ_KERNEL_HANDLE; #endif NTSTATUS rcNt = NtCreateFile(&hFile, GENERIC_READ | SYNCHRONIZE, &ObjAttr, &Ios, NULL /* Allocation Size*/, FILE_ATTRIBUTE_NORMAL, FILE_SHARE_READ, FILE_OPEN, FILE_NON_DIRECTORY_FILE | FILE_SYNCHRONOUS_IO_NONALERT, NULL /*EaBuffer*/, 0 /*EaLength*/); if (NT_SUCCESS(rcNt)) rcNt = Ios.Status; if (!NT_SUCCESS(rcNt)) return supHardNtVpSetInfo1(pErrInfo, VERR_SUP_VP_IMAGE_FILE_OPEN_ERROR, "Error opening image for scanning: %#x (name %ls)", rcNt, pUniStrPath->Buffer); /* * Figure out validation flags we'll be using and create the reader * for this image. */ uint32_t fFlags = fDll ? SUPHNTVI_F_TRUSTED_INSTALLER_OR_SIMILAR_OWNER | SUPHNTVI_F_ALLOW_CAT_FILE_VERIFICATION : SUPHNTVI_F_REQUIRE_BUILD_CERT; if (f32bitResourceDll) fFlags |= SUPHNTVI_F_IGNORE_ARCHITECTURE; PSUPHNTVIRDR pNtViRdr; int rc = supHardNtViRdrCreate(hFile, pUniStrPath->Buffer, fFlags, &pNtViRdr); if (RT_FAILURE(rc)) { NtClose(hFile); return rc; } /* * Finally, open the image with the loader */ RTLDRMOD hLdrMod; RTLDRARCH enmArch = fFlags & SUPHNTVI_F_RC_IMAGE ? RTLDRARCH_X86_32 : RTLDRARCH_HOST; if (fFlags & SUPHNTVI_F_IGNORE_ARCHITECTURE) enmArch = RTLDRARCH_WHATEVER; rc = RTLdrOpenWithReader(&pNtViRdr->Core, RTLDR_O_FOR_VALIDATION, enmArch, &hLdrMod, pErrInfo); if (RT_FAILURE(rc)) return supHardNtVpAddInfo1(pErrInfo, rc, "RTLdrOpenWithReader failed: %Rrc (Image='%ls').", rc, pUniStrPath->Buffer); /* * Fill in the cache entry. */ pEntry->pszName = pszName; pEntry->hLdrMod = hLdrMod; pEntry->pNtViRdr = pNtViRdr; pEntry->hFile = hFile; pEntry->pbBits = NULL; pEntry->fVerified = false; pEntry->fValidBits = false; pEntry->uImageBase = ~(uintptr_t)0; #ifdef IN_SUP_HARDENED_R3 /* * Log the image timestamp when in the hardened exe. */ uint64_t uTimestamp = 0; rc = RTLdrQueryProp(hLdrMod, RTLDRPROP_TIMESTAMP_SECONDS, &uTimestamp, sizeof(uint64_t)); SUP_DPRINTF(("%s: timestamp %#llx (rc=%Rrc)\n", pszName, uTimestamp, rc)); #endif return VINF_SUCCESS; } #ifdef IN_RING3 /** * Opens a loader cache entry. * * Currently this is only used by the import code for getting NTDLL. * * @returns VBox status code. * @param pszName The DLL name. Must be one from the * g_apszSupNtVpAllowedDlls array. * @param ppEntry Where to return the entry we've opened/found. * @param pErrInfo Optional buffer where to return additional error * information. */ DECLHIDDEN(int) supHardNtLdrCacheOpen(const char *pszName, PSUPHNTLDRCACHEENTRY *ppEntry, PRTERRINFO pErrInfo) { /* * Locate the dll. */ uint32_t i = 0; while ( i < RT_ELEMENTS(g_apszSupNtVpAllowedDlls) && strcmp(pszName, g_apszSupNtVpAllowedDlls[i])) i++; if (i >= RT_ELEMENTS(g_apszSupNtVpAllowedDlls)) return VERR_FILE_NOT_FOUND; pszName = g_apszSupNtVpAllowedDlls[i]; /* * Try the cache. */ *ppEntry = supHardNtLdrCacheLookupEntry(pszName); if (*ppEntry) return VINF_SUCCESS; /* * Not in the cache, so open it. * Note! We cannot assume that g_System32NtPath has been initialized at this point. */ if (g_cSupNtVpLdrCacheEntries >= RT_ELEMENTS(g_aSupNtVpLdrCacheEntries)) return VERR_INTERNAL_ERROR_3; static WCHAR s_wszSystem32[] = L"\\SystemRoot\\System32\\"; WCHAR wszPath[64]; memcpy(wszPath, s_wszSystem32, sizeof(s_wszSystem32)); RTUtf16CatAscii(wszPath, sizeof(wszPath), pszName); UNICODE_STRING UniStr; UniStr.Buffer = wszPath; UniStr.Length = (USHORT)(RTUtf16Len(wszPath) * sizeof(WCHAR)); UniStr.MaximumLength = UniStr.Length + sizeof(WCHAR); int rc = supHardNtLdrCacheNewEntry(&g_aSupNtVpLdrCacheEntries[g_cSupNtVpLdrCacheEntries], pszName, &UniStr, true /*fDll*/, false /*f32bitResourceDll*/, pErrInfo); if (RT_SUCCESS(rc)) { *ppEntry = &g_aSupNtVpLdrCacheEntries[g_cSupNtVpLdrCacheEntries]; g_cSupNtVpLdrCacheEntries++; return VINF_SUCCESS; } return rc; } #endif /* IN_RING3 */ /** * Opens all the images with the IPRT loader, setting both, hFile, pNtViRdr and * hLdrMod for each image. * * @returns VBox status code. * @param pThis The process scanning state structure. */ static int supHardNtVpOpenImages(PSUPHNTVPSTATE pThis) { unsigned i = pThis->cImages; while (i-- > 0) { PSUPHNTVPIMAGE pImage = &pThis->aImages[i]; #ifdef IN_RING3 /* * Try the cache first. */ pImage->pCacheEntry = supHardNtLdrCacheLookupEntry(pImage->pszName); if (pImage->pCacheEntry) continue; /* * Not in the cache, so load it into the cache. */ if (g_cSupNtVpLdrCacheEntries >= RT_ELEMENTS(g_aSupNtVpLdrCacheEntries)) return supHardNtVpSetInfo2(pThis, VERR_INTERNAL_ERROR_3, "Loader cache overflow."); pImage->pCacheEntry = &g_aSupNtVpLdrCacheEntries[g_cSupNtVpLdrCacheEntries]; #else /* * In ring-0 we don't have a cache at the moment (resource reasons), so * we have a static cache entry in each image structure that we use instead. */ pImage->pCacheEntry = &pImage->CacheEntry; #endif int rc = supHardNtLdrCacheNewEntry(pImage->pCacheEntry, pImage->pszName, &pImage->Name.UniStr, pImage->fDll, pImage->f32bitResourceDll, pThis->pErrInfo); if (RT_FAILURE(rc)) return rc; #ifdef IN_RING3 g_cSupNtVpLdrCacheEntries++; #endif } return VINF_SUCCESS; } /** * Check the integrity of the executable of the process. * * @returns VBox status code. * @param pThis The process scanning state structure. Details * about images are added to this. The hProcess * member holds the handle to the process that is * to be verified. */ static int supHardNtVpCheckExe(PSUPHNTVPSTATE pThis) { /* * Make sure there is exactly one executable image. */ unsigned cExecs = 0; unsigned iExe = ~0U; unsigned i = pThis->cImages; while (i-- > 0) { if (!pThis->aImages[i].fDll) { cExecs++; iExe = i; } } if (cExecs == 0) return supHardNtVpSetInfo2(pThis, VERR_SUP_VP_NO_FOUND_NO_EXE_MAPPING, "No executable mapping found in the virtual address space."); if (cExecs != 1) return supHardNtVpSetInfo2(pThis, VERR_SUP_VP_FOUND_MORE_THAN_ONE_EXE_MAPPING, "Found more than one executable mapping in the virtual address space."); PSUPHNTVPIMAGE pImage = &pThis->aImages[iExe]; /* * Check that it matches the executable image of the process. */ int rc; ULONG cbUniStr = sizeof(UNICODE_STRING) + RTPATH_MAX * sizeof(RTUTF16); PUNICODE_STRING pUniStr = (PUNICODE_STRING)RTMemAllocZ(cbUniStr); if (!pUniStr) return supHardNtVpSetInfo2(pThis, VERR_SUP_VP_NO_MEMORY, "Error allocating %zu bytes for process name.", cbUniStr); ULONG cbIgn = 0; NTSTATUS rcNt = NtQueryInformationProcess(pThis->hProcess, ProcessImageFileName, pUniStr, cbUniStr - sizeof(WCHAR), &cbIgn); if (NT_SUCCESS(rcNt)) { pUniStr->Buffer[pUniStr->Length / sizeof(WCHAR)] = '\0'; if (supHardNtVpArePathsEqual(pUniStr, &pImage->Name.UniStr)) rc = VINF_SUCCESS; else rc = supHardNtVpSetInfo2(pThis, VERR_SUP_VP_EXE_VS_PROC_NAME_MISMATCH, "Process image name does not match the exectuable we found: %ls vs %ls.", pUniStr->Buffer, pImage->Name.UniStr.Buffer); } else rc = supHardNtVpSetInfo2(pThis, VERR_SUP_VP_NT_QI_PROCESS_NM_ERROR, "NtQueryInformationProcess/ProcessImageFileName failed: %#x", rcNt); RTMemFree(pUniStr); if (RT_FAILURE(rc)) return rc; /* * Validate the signing of the executable image. * This will load the fDllCharecteristics and fImageCharecteristics members we use below. */ rc = supHardNtVpVerifyImage(pThis, pImage); if (RT_FAILURE(rc)) return rc; /* * Check linking requirements. * This query is only available using the current process pseudo handle on * older windows versions. The cut-off seems to be Vista. */ SECTION_IMAGE_INFORMATION ImageInfo; rcNt = NtQueryInformationProcess(pThis->hProcess, ProcessImageInformation, &ImageInfo, sizeof(ImageInfo), NULL); if (!NT_SUCCESS(rcNt)) { if ( rcNt == STATUS_INVALID_PARAMETER && g_uNtVerCombined < SUP_NT_VER_VISTA && pThis->hProcess != NtCurrentProcess() ) return VINF_SUCCESS; return supHardNtVpSetInfo2(pThis, VERR_SUP_VP_NT_QI_PROCESS_IMG_INFO_ERROR, "NtQueryInformationProcess/ProcessImageInformation failed: %#x hProcess=%#x", rcNt, pThis->hProcess); } #ifndef VBOX_WITHOUT_WINDOWS_KERNEL_CODE_SIGNING_CERT /* A kernel code signing cert is only via way to use /IntegrityCheck. */ if ( !(ImageInfo.DllCharacteristics & IMAGE_DLLCHARACTERISTICS_FORCE_INTEGRITY)) return supHardNtVpSetInfo2(pThis, VERR_SUP_VP_EXE_MISSING_FORCE_INTEGRITY, "EXE DllCharacteristics=%#x, expected IMAGE_DLLCHARACTERISTICS_FORCE_INTEGRITY to be set.", ImageInfo.DllCharacteristics); #endif if (!(ImageInfo.DllCharacteristics & IMAGE_DLLCHARACTERISTICS_DYNAMIC_BASE)) return supHardNtVpSetInfo2(pThis, VERR_SUP_VP_EXE_MISSING_DYNAMIC_BASE, "EXE DllCharacteristics=%#x, expected IMAGE_DLLCHARACTERISTICS_DYNAMIC_BASE to be set.", ImageInfo.DllCharacteristics); if (!(ImageInfo.DllCharacteristics & IMAGE_DLLCHARACTERISTICS_NX_COMPAT)) return supHardNtVpSetInfo2(pThis, VERR_SUP_VP_EXE_MISSING_NX_COMPAT, "EXE DllCharacteristics=%#x, expected IMAGE_DLLCHARACTERISTICS_NX_COMPAT to be set.", ImageInfo.DllCharacteristics); if (pImage->fDllCharecteristics != ImageInfo.DllCharacteristics) return supHardNtVpSetInfo2(pThis, VERR_SUP_VP_DLL_CHARECTERISTICS_MISMATCH, "EXE Info.DllCharacteristics=%#x fDllCharecteristics=%#x.", ImageInfo.DllCharacteristics, pImage->fDllCharecteristics); if (pImage->fImageCharecteristics != ImageInfo.ImageCharacteristics) return supHardNtVpSetInfo2(pThis, VERR_SUP_VP_DLL_CHARECTERISTICS_MISMATCH, "EXE Info.ImageCharacteristics=%#x fImageCharecteristics=%#x.", ImageInfo.ImageCharacteristics, pImage->fImageCharecteristics); return VINF_SUCCESS; } /** * Check the integrity of the DLLs found in the process. * * @returns VBox status code. * @param pThis The process scanning state structure. Details * about images are added to this. The hProcess * member holds the handle to the process that is * to be verified. */ static int supHardNtVpCheckDlls(PSUPHNTVPSTATE pThis) { /* * Check for duplicate entries (paranoia). */ uint32_t i = pThis->cImages; while (i-- > 1) { const char *pszName = pThis->aImages[i].pszName; uint32_t j = i; while (j-- > 0) if (pThis->aImages[j].pszName == pszName) return supHardNtVpSetInfo2(pThis, VERR_SUP_VP_DUPLICATE_DLL_MAPPING, "Duplicate image entries for %s: %ls and %ls", pszName, pThis->aImages[i].Name.UniStr.Buffer, pThis->aImages[j].Name.UniStr.Buffer); } /* * Check that both ntdll and kernel32 are present. * ASSUMES the entries in g_apszSupNtVpAllowedDlls are all lower case. */ uint32_t iNtDll = UINT32_MAX; uint32_t iKernel32 = UINT32_MAX; i = pThis->cImages; while (i-- > 0) if (suplibHardenedStrCmp(pThis->aImages[i].pszName, "ntdll.dll") == 0) iNtDll = i; else if (suplibHardenedStrCmp(pThis->aImages[i].pszName, "kernel32.dll") == 0) iKernel32 = i; if (iNtDll == UINT32_MAX) return supHardNtVpSetInfo2(pThis, VERR_SUP_VP_NO_NTDLL_MAPPING, "The process has no NTDLL.DLL."); if (iKernel32 == UINT32_MAX && ( pThis->enmKind == SUPHARDNTVPKIND_SELF_PURIFICATION || pThis->enmKind == SUPHARDNTVPKIND_SELF_PURIFICATION_LIMITED)) return supHardNtVpSetInfo2(pThis, VERR_SUP_VP_NO_KERNEL32_MAPPING, "The process has no KERNEL32.DLL."); else if (iKernel32 != UINT32_MAX && pThis->enmKind == SUPHARDNTVPKIND_CHILD_PURIFICATION) return supHardNtVpSetInfo2(pThis, VERR_SUP_VP_KERNEL32_ALREADY_MAPPED, "The process already has KERNEL32.DLL loaded."); /* * Verify that the DLLs are correctly signed (by MS). */ i = pThis->cImages; while (i-- > 0) { int rc = supHardNtVpVerifyImage(pThis, &pThis->aImages[i]); if (RT_FAILURE(rc)) return rc; } return VINF_SUCCESS; } #ifdef IN_RING3 /** * Verifies that we don't have any inheritable handles around, other than a few * ones for file and event objects. * * When finding an inheritable handle of a different type, it will change it to * non-inhertiable. This must NOT be called in the final process prior to * opening the device! * * @returns VBox status code * @param pThis The process scanning state structure. */ static int supHardNtVpCheckHandles(PSUPHNTVPSTATE pThis) { SUP_DPRINTF(("supHardNtVpCheckHandles:\n")); /* * Take a snapshot of all the handles in the system. * (Because the current process handle snapshot was added in Windows 8, * so we cannot use that yet.) */ uint32_t cbBuf = _256K; uint8_t *pbBuf = (uint8_t *)RTMemAlloc(cbBuf); ULONG cbNeeded = cbBuf; NTSTATUS rcNt = NtQuerySystemInformation(SystemExtendedHandleInformation, pbBuf, cbBuf, &cbNeeded); if (!NT_SUCCESS(rcNt)) { while ( rcNt == STATUS_INFO_LENGTH_MISMATCH && cbNeeded > cbBuf && cbBuf <= _32M) { cbBuf = RT_ALIGN_32(cbNeeded + _4K, _64K); RTMemFree(pbBuf); pbBuf = (uint8_t *)RTMemAlloc(cbBuf); if (!pbBuf) return supHardNtVpSetInfo2(pThis, VERR_SUP_VP_NO_MEMORY, "Failed to allocate %zu bytes querying handles.", cbBuf); rcNt = NtQuerySystemInformation(SystemExtendedHandleInformation, pbBuf, cbBuf, &cbNeeded); } if (!NT_SUCCESS(rcNt)) { RTMemFree(pbBuf); return supHardNtVpSetInfo2(pThis, VERR_SUP_VP_NO_MEMORY, "Failed to allocate %zu bytes querying handles.", cbBuf); } } /* * Examine the snapshot for handles for this process. */ int rcRet = VINF_SUCCESS; HANDLE const idProcess = RTNtCurrentTeb()->ClientId.UniqueProcess; SYSTEM_HANDLE_INFORMATION_EX const *pInfo = (SYSTEM_HANDLE_INFORMATION_EX const *)pbBuf; ULONG_PTR i = pInfo->NumberOfHandles; AssertRelease(RT_UOFFSETOF_DYN(SYSTEM_HANDLE_INFORMATION_EX, Handles[i]) == cbNeeded); while (i-- > 0) { SYSTEM_HANDLE_ENTRY_INFO_EX const *pHandleInfo = &pInfo->Handles[i]; if ( (pHandleInfo->HandleAttributes & OBJ_INHERIT) && pHandleInfo->UniqueProcessId == idProcess) { ULONG cbNeeded2 = 0; rcNt = NtQueryObject(pHandleInfo->HandleValue, ObjectTypeInformation, pThis->abMemory, sizeof(pThis->abMemory), &cbNeeded2); if (NT_SUCCESS(rcNt)) { POBJECT_TYPE_INFORMATION pTypeInfo = (POBJECT_TYPE_INFORMATION)pThis->abMemory; if ( pTypeInfo->TypeName.Length == sizeof(L"File") - sizeof(wchar_t) && memcmp(pTypeInfo->TypeName.Buffer, L"File", sizeof(L"File") - sizeof(wchar_t)) == 0) SUP_DPRINTF(("supHardNtVpCheckHandles: Inheritable file handle: %p\n", pHandleInfo->HandleValue)); else if ( pTypeInfo->TypeName.Length == sizeof(L"Event") - sizeof(wchar_t) && memcmp(pTypeInfo->TypeName.Buffer, L"Event", sizeof(L"Event") - sizeof(wchar_t)) == 0) SUP_DPRINTF(("supHardNtVpCheckHandles: Inheritable event handle: %p\n", pHandleInfo->HandleValue)); else { OBJECT_HANDLE_FLAG_INFORMATION SetInfo; SetInfo.Inherit = FALSE; SetInfo.ProtectFromClose = FALSE; rcNt = NtSetInformationObject(pHandleInfo->HandleValue, ObjectHandleFlagInformation, &SetInfo, sizeof(SetInfo)); if (NT_SUCCESS(rcNt)) { SUP_DPRINTF(("supHardNtVpCheckHandles: Marked %ls handle non-inheritable: %p\n", pTypeInfo->TypeName.Buffer, pHandleInfo->HandleValue)); pThis->cFixes++; } else { rcRet = supHardNtVpSetInfo2(pThis, VERR_SUP_VP_SET_HANDLE_NOINHERIT, "NtSetInformationObject(%p,,,) -> %#x", pHandleInfo->HandleValue, rcNt); break; } } } else { rcRet = supHardNtVpSetInfo2(pThis, VERR_SUP_VP_QUERY_HANDLE_TYPE, "NtQueryObject(%p,,,,) -> %#x", pHandleInfo->HandleValue, rcNt); break; } } } RTMemFree(pbBuf); return rcRet; } #endif /* IN_RING3 */ /** * Verifies the given process. * * The following requirements are checked: * - The process only has one thread, the calling thread. * - The process has no debugger attached. * - The executable image of the process is verified to be signed with * certificate known to this code at build time. * - The executable image is one of a predefined set. * - The process has only a very limited set of system DLLs loaded. * - The system DLLs signatures check out fine. * - The only executable memory in the process belongs to the system DLLs and * the executable image. * * @returns VBox status code. * @param hProcess The process to verify. * @param hThread A thread in the process (the caller). * @param enmKind The kind of process verification to perform. * @param fFlags Valid combination of SUPHARDNTVP_F_XXX flags. * @param pErrInfo Pointer to error info structure. Optional. * @param pcFixes Where to return the number of fixes made during * purification. Optional. */ DECLHIDDEN(int) supHardenedWinVerifyProcess(HANDLE hProcess, HANDLE hThread, SUPHARDNTVPKIND enmKind, uint32_t fFlags, uint32_t *pcFixes, PRTERRINFO pErrInfo) { if (pcFixes) *pcFixes = 0; /* * Some basic checks regarding threads and debuggers. We don't need * allocate any state memory for these. */ int rc = VINF_SUCCESS; if ( enmKind != SUPHARDNTVPKIND_CHILD_PURIFICATION && enmKind != SUPHARDNTVPKIND_SELF_PURIFICATION_LIMITED) rc = supHardNtVpThread(hProcess, hThread, pErrInfo); if (RT_SUCCESS(rc)) rc = supHardNtVpDebugger(hProcess, pErrInfo); if (RT_SUCCESS(rc)) { /* * Allocate and initialize memory for the state. */ PSUPHNTVPSTATE pThis = (PSUPHNTVPSTATE)RTMemAllocZ(sizeof(*pThis)); if (pThis) { pThis->enmKind = enmKind; pThis->fFlags = fFlags; pThis->rcResult = VINF_SUCCESS; pThis->hProcess = hProcess; pThis->pErrInfo = pErrInfo; /* * Perform the verification. */ rc = supHardNtVpScanVirtualMemory(pThis, hProcess); if (RT_SUCCESS(rc)) rc = supHardNtVpOpenImages(pThis); if (RT_SUCCESS(rc)) rc = supHardNtVpCheckExe(pThis); if (RT_SUCCESS(rc)) rc = supHardNtVpCheckDlls(pThis); #ifdef IN_RING3 if (enmKind == SUPHARDNTVPKIND_SELF_PURIFICATION_LIMITED) rc = supHardNtVpCheckHandles(pThis); #endif if (pcFixes) *pcFixes = pThis->cFixes; /* * Clean up the state. */ #ifdef IN_RING0 for (uint32_t i = 0; i < pThis->cImages; i++) supHardNTLdrCacheDeleteEntry(&pThis->aImages[i].CacheEntry); #endif RTMemFree(pThis); } else rc = supHardNtVpSetInfo1(pErrInfo, VERR_SUP_VP_NO_MEMORY_STATE, "Failed to allocate %zu bytes for state structures.", sizeof(*pThis)); } return rc; }