SUPHardenedVerifyProcess-win.cpp revision 9997cd77b8e044f801beb055c5e2a9f0faf59780
/* $Id$ */
/** @file
*/
/*
* Copyright (C) 2006-2014 Oracle Corporation
*
* This file is part of VirtualBox Open Source Edition (OSE), as
* available from http://www.virtualbox.org. This file is free software;
* General Public License (GPL) as published by the Free Software
* Foundation, in version 2 as it comes in the "COPYING" file of the
* VirtualBox OSE distribution. VirtualBox OSE is distributed in the
* hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
*
* The contents of this file may alternatively be used under the terms
* of the Common Development and Distribution License Version 1.0
* (CDDL) only, as it comes in the "COPYING.CDDL" file of the
* VirtualBox OSE 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.
*/
/*******************************************************************************
* Header Files *
*******************************************************************************/
#ifdef IN_RING0
# define IPRT_NT_MAP_TO_ZW
# include <ntimage.h>
#else
#endif
#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. */
/** The size of the region. */
/** The protection of the region. */
/** Pointer to a virtual address space region. */
typedef SUPHNTVPREGION *PSUPHNTVPREGION;
/**
* Virtual address space image information.
*/
typedef struct SUPHNTVPIMAGE
{
/** The base address of the image. */
/** The size of the image mapping. */
/** The name from the allowed lists. */
const char *pszName;
/** Name structure for NtQueryVirtualMemory/MemorySectionName. */
struct
{
/** The full unicode name. */
/** Buffer space. */
} Name;
/** The number of mapping regions. */
/** Mapping regions. */
/** The image characteristics from the FileHeader. */
/** The DLL characteristics from the OptionalHeader. */
/** 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. */
/** This may be a 32-bit resource DLL. */
bool f32bitResourceDll;
/** Pointer to the loader cache entry for the image. */
#ifdef IN_RING0
/** In ring-0 we don't currently cache images, so put it here. */
#endif
/** 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. */
/** Combination of SUPHARDNTVP_F_XXX. */
/** The result. */
int rcResult;
/** Number of fixes we've done.
* Only applicable in the purification modes. */
/** Number of images in aImages. */
/** The index of the last image we looked up. */
/** The process handle. */
/** 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. */
#ifdef VBOX_PERMIT_VERIFIER_DLL
+ 1
#endif
#ifdef VBOX_PERMIT_MORE
+ 5
#endif
+ 16
#endif
];
/** Memory compare scratch buffer.*/
/** File compare scratch buffer.*/
/** Section headers for use when comparing file and loaded image. */
/** Pointer to the error info. */
/** 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
"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 SUPR3HardenedVerify.cpp.
*/
static const char *g_apszSupNtVpAllowedVmExes[] =
{
"VBoxHeadless.exe",
"VirtualBox.exe",
"VBoxSDL.exe",
"VBoxNetDHCP.exe",
"VBoxNetNAT.exe",
"tstMicro.exe",
"tstPDMAsyncCompletion.exe",
"tstPDMAsyncCompletionStress.exe",
"tstVMM.exe",
"tstVMREQ.exe",
"tstCFGM.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
#else
#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 pszErr Where to return error details.
* @param cbErr Size of the buffer @a pszErr points to.
* @param rc The status to return.
* @param pszMsg The format string for the message.
* @param ... The arguments for the format string.
*/
{
#ifdef IN_RING3
#endif
return rc;
}
/**
* Fills in error information.
*
* @returns @a rc.
* @param pThis The process validator instance.
* @param pszErr Where to return error details.
* @param cbErr Size of the buffer @a pszErr points to.
* @param rc The status to return.
* @param pszMsg The format string for the message.
* @param ... The arguments for the format string.
*/
{
#ifdef IN_RING3
#endif
#ifdef IN_RING0
#else
{
}
else
{
}
#endif
}
{
return pImage->pCacheEntry->pNtViRdr->Core.pfnRead(&pImage->pCacheEntry->pNtViRdr->Core, pvBuf, cbRead, off);
}
{
#ifdef IN_RING0
/* ASSUMES hProcess is the current process. */
/** @todo use MmCopyVirtualMemory where available! */
if (RT_SUCCESS(rc))
return STATUS_SUCCESS;
return STATUS_ACCESS_DENIED;
#else
return rcNt;
#endif
}
#ifdef IN_RING3
static NTSTATUS supHardNtVpFileMemRestore(PSUPHNTVPSTATE pThis, PVOID pvRestoreAddr, uint8_t const *pbFile, uint32_t cbToRestore,
{
NTSTATUS rcNt = NtProtectVirtualMemory(pThis->hProcess, &pvProt, &cbProt, PAGE_READWRITE, &fOldProt);
if (NT_SUCCESS(rcNt))
{
NTSTATUS rcNt2 = NtProtectVirtualMemory(pThis->hProcess, &pvProt, &cbProt, fCorrectProtection, &fOldProt);
if (NT_SUCCESS(rcNt))
}
return rcNt;
}
#endif /* IN_RING3 */
typedef struct SUPHNTVPSKIPAREA
{
typedef SUPHNTVPSKIPAREA *PSUPHNTVPSKIPAREA;
{
AssertCompileAdjacentMembers(SUPHNTVPSTATE, abMemory, abFile); /* Use both the memory and file buffers here. Parfait might hate me for this... */
while (cb > 0)
{
/* Clipping. */
if (cSkipAreas)
{
uint32_t i = cSkipAreas;
while (i-- > 0)
{
{
{
}
{
}
else
{
cbThis = 0;
break;
}
}
}
}
/* Read the memory. */
if (!NT_SUCCESS(rcNt))
"%s: Error reading %#x bytes at %p (rva %#x, #%u, %.8s) from memory: %#x",
/* Do the compare. */
{
SUP_DPRINTF(("%s: Differences in section #%u (%s) between file and memory:\n", pImage->pszName, iSh + 1, pachSectNm));
off++;
SUP_DPRINTF((" %p / %#09x: %02x != %02x\n",
{
SUP_DPRINTF((" %p / %#09x: %02x != %02x\n",
cDiffs++;
}
#ifdef IN_RING3
{
if (NT_SUCCESS(rcNt))
else
"%s: Failed to restore %#x bytes at %p (%#x, #%u, %s): %#x (cDiffs=%#x, first=%#x)",
}
else
#endif /* IN_RING3 */
"%s: %u differences between %#x and %#x in #%u (%.8s), first: %02x != %02x",
}
/* Advance. The clipping makes it a little bit complicated. */
break;
}
return VINF_SUCCESS;
}
{
if (!cb)
return VINF_SUCCESS;
return VINF_SUCCESS;
{
{
&& ( fProt != PAGE_READWRITE
"%s: RVA range %#x-%#x protection is %#x, expected %#x. (cb=%#x)",
return VINF_SUCCESS;
#if 0 /* This shouldn't ever be necessary. */
{
return VINF_SUCCESS;
}
#endif
}
}
return supHardNtVpSetInfo2(pThis, cbOrg == cb ? VERR_SUP_VP_SECTION_NOT_MAPPED : VERR_SUP_VP_SECTION_NOT_FULLY_MAPPED,
}
{
{
for (;;)
{
char chLeft = *pszImageNm++;
{
{
if ( chRight == '\0'
&& chLeft == '.'
&& pszImageNm[0] == 'd'
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.
*/
{
/*
* Check out the hint first.
*/
/*
* Linear array search next.
*/
while (i-- > 0)
{
pThis->iImageHint = i;
}
/* No cigar. */
return NULL;
}
/**
* @callback_method_impl{FNRTLDRIMPORT}
*/
static DECLCALLBACK(int) supHardNtVpGetImport(RTLDRMOD hLdrMod, const char *pszModule, const char *pszSymbol, unsigned uSymbol,
{
/*SUP_DPRINTF(("supHardNtVpGetImport: %s / %#x / %s.\n", pszModule, uSymbol, pszSymbol));*/
int rc = VERR_MODULE_NOT_FOUND;
if (pImage)
{
if (RT_SUCCESS(rc))
return rc;
}
/*
* API set hacks.
*/
{
{
if (pImage)
{
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)
{
if (RT_SUCCESS(rc))
{
if (pImage)
{
if (RT_SUCCESS(rc))
return rc;
SUP_DPRINTF(("supHardNtVpGetImport: Failed to find symbol '%s' in '%s' (forwarded from %s / %s): %Rrc\n",
if (rc == VERR_LDR_FORWARDER)
}
else
SUP_DPRINTF(("supHardNtVpGetImport: Failed to find forwarder module '%s' (%#x / %s; originally %s / %#x / %s): %Rrc\n",
}
else
SUP_DPRINTF(("supHardNtVpGetImport: RTLdrQueryForwarderInfo failed on symbol %#x/'%s' in '%s': %Rrc\n",
}
else
SUP_DPRINTF(("supHardNtVpGetImport: Failed to find symbol %#x / '%s' in '%s': %Rrc\n",
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.
* @param hProcess Handle to the process.
* @param pErrInfo Pointer to error info structure. Optional.
*/
static int supHardNtVpVerifyImageMemoryCompare(PSUPHNTVPSTATE pThis, PSUPHNTVPIMAGE pImage, HANDLE hProcess, PRTERRINFO pErrInfo)
{
/*
* Read and find the file headers.
*/
if (RT_FAILURE(rc))
{
}
/*
* Do basic header validation.
*/
#ifdef RT_ARCH_AMD64
#else
#endif
if (pNtHdrs->FileHeader.SizeOfOptionalHeader != (fIs32Bit ? sizeof(IMAGE_OPTIONAL_HEADER32) : sizeof(IMAGE_OPTIONAL_HEADER64)))
"%s: Unexpected optional header size: %#x",
if (pNtHdrs->OptionalHeader.Magic != (fIs32Bit ? IMAGE_NT_OPTIONAL_HDR32_MAGIC : IMAGE_NT_OPTIONAL_HDR64_MAGIC))
uint32_t cDirs = (fIs32Bit ? pNtHdrs32->OptionalHeader.NumberOfRvaAndSizes : pNtHdrs->OptionalHeader.NumberOfRvaAndSizes);
/*
* Before we start comparing things, store what we need to know from the headers.
*/
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)
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)
"%s: SizeOfImage (%#x) isn't close enough to the mapping size (%#x)",
"%s: SizeOfImage (%#x) differs from what RTLdrSize returns (%#zx)",
uint32_t const cbSectAlign = fIs32Bit ? pNtHdrs32->OptionalHeader.SectionAlignment : pNtHdrs->OptionalHeader.SectionAlignment;
if ( !RT_IS_POWER_OF_TWO(cbSectAlign)
|| cbSectAlign < PAGE_SIZE
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)
"%s: Unexpected FileAlignment value: %#x (cbSectAlign=%#x)",
uint32_t const cbHeaders = fIs32Bit ? pNtHdrs32->OptionalHeader.SizeOfHeaders : pNtHdrs->OptionalHeader.SizeOfHeaders;
+ sizeof(IMAGE_SECTION_HEADER) * cSections;
"%s: Headers are too small: %#x < %#x (cSections=%#x)",
"%s: Headers are larger than expected: %#x/%#x (expected max %zx)",
/*
* Save some header fields we might be using later on.
*/
pImage->fDllCharecteristics = fIs32Bit ? pNtHdrs32->OptionalHeader.DllCharacteristics : pNtHdrs->OptionalHeader.DllCharacteristics;
/*
* Correct the apisetschema image base, size and region rva.
*/
{
}
/*
* Get relocated bits.
*/
rc = supHardNtLdrCacheEntryGetBits(pImage->pCacheEntry, &pbBits, pImage->uImageBase, NULL /*pfnGetImport*/, pThis,
else
rc = supHardNtLdrCacheEntryGetBits(pImage->pCacheEntry, &pbBits, pImage->uImageBase, supHardNtVpGetImport, pThis,
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)
else
}
/*
* Figure out areas we should skip during comparison.
*/
uint32_t cSkipAreas = 0;
if (pImage->fNtCreateSectionPatch)
{
{
/* 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);
/* Ignore our LdrLoadDll hack. */
if (RT_FAILURE(rc))
return supHardNtVpSetInfo2(pThis, rc, "%s: Failed to find 'LdrLoadDll': %Rrc", pImage->pszName, rc);
}
/* 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);
/* 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))
{
}
}
/*
* 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;
if (RT_FAILURE(rc))
return rc;
}
/*
* Validate sections:
* - Check them against the mapping regions.
* - Check section bits according to enmKind.
*/
{
/* Validate the section. */
"%s: Section %u: Invalid virtual address: %#x (uRva=%#x, cbImage=%#x, cbSectAlign=%#x)",
"%s: Section %u: Invalid virtual size: %#x (uSectRva=%#x, uRva=%#x, cbImage=%#x)",
"%s: Section %u: Invalid file size: %#x (cbMap=%#x, uSectRva=%#x)",
/* Validate the protection and bits. */
if (!pImage->fApiSetSchemaOnlySection1 || i == 0)
{
switch (pThis->aSecHdrs[i].Characteristics & (IMAGE_SCN_MEM_EXECUTE | IMAGE_SCN_MEM_READ | IMAGE_SCN_MEM_WRITE))
{
case IMAGE_SCN_MEM_READ:
break;
&& !suplibHardenedMemComp(pThis->aSecHdrs[i].Name, ".mrdata", 8)) /* w8.1, ntdll. Changed by proc init. */
break;
break;
case IMAGE_SCN_MEM_EXECUTE:
break;
/* Only the executable is allowed to have this section,
and it's protected after we're done patching. */
{
else
break;
}
default:
"%s: Section %u: Unexpected characteristics: %#x (uSectRva=%#x, cbMap=%#x)",
}
/* 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). */
|| (pThis->aSecHdrs[i].Characteristics & (IMAGE_SCN_MEM_READ | IMAGE_SCN_MEM_WRITE)) == IMAGE_SCN_MEM_READ
{
rc = VINF_SUCCESS;
if (RT_SUCCESS(rc))
if (RT_SUCCESS(rc))
{
if (cbMapAligned > cbMap)
}
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;
}
/* Advance the RVA. */
}
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.
* @param hProcess Handle to the process.
* @param hFile Handle to the image file.
*/
{
/*
* Validate the file signature first, then do the memory compare.
*/
int rc;
{
rc = supHardNtLdrCacheEntryVerify(pImage->pCacheEntry, pImage->Name.UniStr.Buffer, pThis->pErrInfo);
if (RT_SUCCESS(rc))
}
else
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.
*/
{
/*
* 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?
*/
NTSTATUS rcNt = NtQueryInformationThread(hThread, ThreadAmILastThread, &fAmI, sizeof(fAmI), &cbIgn);
if (!NT_SUCCESS(rcNt))
"NtQueryInformationThread/ThreadAmILastThread -> %#x", rcNt);
if (!fAmI)
"More than one thread in process");
/** @todo Would be nice to verify the relation ship 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.
*/
{
#ifndef VBOX_WITHOUT_DEBUGGER_CHECKS
/*
* Use the ProcessDebugPort request to check there is no debugger
* currently attached to the process.
*/
if (!NT_SUCCESS(rcNt))
"NtQueryInformationProcess/ProcessDebugPort -> %#x", rcNt);
if (uPtr != 0)
"Debugger attached (%#zx)", uPtr);
#endif /* !VBOX_WITHOUT_DEBUGGER_CHECKS */
return VINF_SUCCESS;
}
/**
* Checks whether the path could be containing alternative 8.3 names generated
* by NTFS, FAT, or other similar file systems.
*
* @returns Pointer to the first component that might be an 8.3 name, NULL if
* not 8.3 path.
* @param pwszPath The path to check.
*
* @remarks This is making bad ASSUMPTION wrt to the naming scheme of 8.3 names,
* however, non-tilde 8.3 aliases are probably rare enough to not be
* worth all the extra code necessary to open each path component and
* check if we've got the short name or not.
*/
{
for (;;)
{
if (wc == '~')
{
/* Could check more here before jumping to conclusions... */
}
else if (wc == 0)
break;
}
return NULL;
}
/**
* Fixes up a path possibly containing one or more alternative 8-dot-3 style
* components.
*
* The path is fixed up in place. Errors are ignored.
*
* @param pUniStr The path to fix up. MaximumLength is the max buffer
* length.
*/
{
/*
* We could use FileNormalizedNameInformation here and slap the volume device
* path in front of the result, but it's only supported since windows 8.0
* according to some docs... So we expand all supicious names.
*/
union fix8dot3tmp
{
while (*pwszFix)
{
break;
pwszFixEnd++;
break;
if (!puBuf)
{
if (!puBuf)
break;
}
InitializeObjectAttributes(&ObjAttr, &NtDir, OBJ_CASE_INSENSITIVE, NULL /*hRootDir*/, NULL /*pSecDesc*/);
#ifdef IN_RING0
#endif
&ObjAttr,
&Ios,
NULL /* Allocation Size*/,
NULL /*EaBuffer*/,
0 /*EaLength*/);
if (NT_SUCCESS(rcNt))
{
NULL /* Event */,
NULL /* ApcRoutine */,
NULL /* ApcContext */,
&Ios,
FALSE /*ReturnSingleEntry*/,
FALSE /*RestartScan */);
if (NT_SUCCESS(rcNt) && puBuf->Info.NextEntryOffset == 0) /* There shall only be one entry matching... */
{
while (offName > 0 && puBuf->Info.FileName[offName - 1] != '\\' && puBuf->Info.FileName[offName - 1] != '/')
offName--;
if (cwcNameOld == cwcNameNew)
<= pUniStr->MaximumLength)
{
pwszFixEnd -= cwcNameOld;
pwszFixEnd -= cwcNameNew;
}
/* else: ignore overflow. */
}
/* else: ignore failure. */
}
/* Advance */
}
if (puBuf)
}
/**
* 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.
*/
{
return false;
}
/**
* 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.
*/
{
for (;;)
{
{
return false;
}
if (!ch1)
return true;
}
}
/**
* 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.
*/
{
pTmp->MaximumLength = (USHORT)RT_MIN(_64K - 1, sizeof(pThis->abMemory) - sizeof(*pTmp)) - sizeof(RTUTF16);
}
/*
* Extract the final component.
*/
while ( cwcDirName > 0
&& wc != '/'
&& wc != ':')
{
pwszFilename--;
cwcDirName--;
}
if (!*pwszFilename)
/*
* Drop trailing slashes from the directory name.
*/
while ( cwcDirName > 0
cwcDirName--;
/*
* Match it against known DLLs.
*/
{
/* The directory name must match the one we've got for System32. */
|| suplibHardenedMemComp(pLongName->Buffer, g_System32NtPath.UniStr.Buffer, cwcDirName * sizeof(WCHAR)) )
# ifdef VBOX_PERMIT_MORE
# endif
)
"Expected %ls to be loaded from %ls.",
# ifdef VBOX_PERMIT_MORE
# endif
#endif /* VBOX_PERMIT_VISUAL_STUDIO_PROFILING */
break;
}
{
/*
* Not a known DLL, is it a known executable?
*/
{
break;
}
}
{
/*
* 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
{
SUP_DPRINTF(("supHardNtVpScanVirtualMemory: Unmapping image mem at %p (%p LB %#zx) - '%ls'\n",
if (NT_SUCCESS(rcNt))
return VINF_OBJECT_DESTROYED;
SUP_DPRINTF(("supHardNtVpScanVirtualMemory: NtUnmapViewOfSection(,%p) failed: %#x\n", pMemInfo->AllocationBase, rcNt));
}
#endif
/*
* Special error message if we can.
*/
{
"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. "
return pThis->rcResult = VERR_SUP_VP_SYSFER_DLL; /* Try make sure this is what the user sees first! */
}
}
/*
* Checks for multiple mappings of the same DLL but with different image file paths.
*/
while (i-- > 1)
"Duplicate image entries for %s: %ls and %ls",
/*
* Since it's a new image, we expect to be at the start of the mapping now.
*/
"Invalid AllocationBase/BaseAddress for %s: %p vs %p.",
/*
*/
/*
* Fill in details from the memory info.
*/
pImage->fNtCreateSectionPatch = true;
#ifdef VBOX_PERMIT_MORE
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.
*/
"Base address mismatch for %s: have %p, found %p for region %p LB %#zx.",
/*
* Check for size and rva overflows.
*/
/*
* Record the region.
*/
pImage->fApiSetSchemaOnlySection1 = false;
return VINF_SUCCESS;
}
/**
* 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.
*/
{
SUP_DPRINTF(("supHardNtVpScanVirtualMemory: enmKind=%s\n",
pThis->enmKind == SUPHARDNTVPKIND_CHILD_PURIFICATION ? "CHILD_PURIFICATION" : "SELF_PURIFICATION"));
uint32_t cXpExceptions = 0;
#ifdef VBOX_PERMIT_VERIFIER_DLL
for (uint32_t i = 0; i < 10240; i++)
#else
for (uint32_t i = 0; i < 1024; i++)
#endif
{
MEMORY_BASIC_INFORMATION MemInfo = { 0, 0, 0, 0, 0, 0, 0 };
(void const *)uPtrWhere,
&MemInfo,
sizeof(MemInfo),
&cbActual);
if (!NT_SUCCESS(rcNt))
{
if (rcNt == STATUS_INVALID_PARAMETER)
}
/*
* Record images.
*/
{
(void const *)uPtrWhere,
&cbActual);
if (!NT_SUCCESS(rcNt))
pThis->aImages[iImg].Name.UniStr.Buffer[pThis->aImages[iImg].Name.UniStr.Length / sizeof(WCHAR)] = '\0';
? " *%p-%p %#06x/%#06x %#09x %ls\n"
: " %p-%p %#06x/%#06x %#09x %ls\n",
/* New or existing image? */
bool fNew = true;
while (iSearch-- > 0)
if (supHardNtVpAreUniStringsEqual(&pThis->aImages[iSearch].Name.UniStr, &pThis->aImages[iImg].Name.UniStr))
{
if (RT_FAILURE(rc))
return rc;
fNew = false;
break;
}
"Unexpected base address match");
if (fNew)
{
if (RT_SUCCESS(rc))
{
if (rc != VINF_OBJECT_DESTROYED)
{
"Internal error: aImages is full.\n");
}
}
#ifdef IN_RING3 /* Continue and add more information if unknown DLLs are found. */
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))
&& cXpExceptions == 0
/* && MemInfo.BaseAddress == pPeb->ReadOnlySharedMemoryBase */
{
}
/*
* Executable memory?
*/
else if (MemInfo.Protect & (PAGE_EXECUTE | PAGE_EXECUTE_READ | PAGE_EXECUTE_READWRITE | PAGE_EXECUTE_WRITECOPY))
{
? " *%p-%p %#06x/%#06x %#09x !!\n"
: " %p-%p %#06x/%#06x %#09x !!\n",
# ifdef IN_RING3
{
/*
* Free any private executable memory (sysplant.sys allocates executable memory).
*/
{
{
SUP_DPRINTF(("supHardNtVpScanVirtualMemory: Freeing exec mem at %p (%p LB %#zx)\n",
if (!NT_SUCCESS(rcNt))
"NtFreeVirtualMemory (%p LB %#zx) failed: %#x",
}
else
{
/* The Trend Micro sakfile.sys and Digital Guardian dgmaster.sys BSOD kludge. */
SUP_DPRINTF(("supHardNtVpScanVirtualMemory: Replacing exec mem at %p (%p LB %#zx)\n",
if (pvCopy)
{
if (!NT_SUCCESS(rcNt))
"Error reading data from original alloc: %#x (%p LB %#zx)",
if (NT_SUCCESS(rcNt))
{
if (!NT_SUCCESS(rcNt))
"NtAllocateVirtualMemory (%p LB %#zx) failed with rcNt=%#x allocating "
"replacement memory for working around buggy protection software. "
"See VBoxStartup.log for more details",
"We wanted NtAllocateVirtualMemory to get us %p LB %#zx, but it returned %p LB %#zx.",
else
{
&cbWritten);
if (!NT_SUCCESS(rcNt))
"NtWriteVirtualMemory (%p LB %#zx) failed: %#x",
}
}
else
"NtFreeVirtualMemory (%p LB %#zx) failed: %#x",
}
else
}
}
/*
* Unmap mapped memory, failing that, drop exec privileges.
*/
{
SUP_DPRINTF(("supHardNtVpScanVirtualMemory: Unmapping exec mem at %p (%p/%p LB %#zx)\n",
if (!NT_SUCCESS(rcNt))
{
if (!NT_SUCCESS(rcNt2))
if (!NT_SUCCESS(rcNt2))
"NtUnmapViewOfSection (%p/%p LB %#zx) failed: %#x (%#x)",
}
}
else
"Unknown executable memory type %#x at %p/%p LB %#zx",
}
else
# endif /* IN_RING3 */
"Found executable memory at %p (%p LB %#zx): type=%#x prot=%#x state=%#x aprot=%#x abase=%p",
# ifndef IN_RING3
# endif
/* Continue add more information about the problematic process. */
}
#endif /* VBOX_PERMIT_VISUAL_STUDIO_PROFILING */
else
? " *%p-%p %#06x/%#06x %#09x\n"
: " %p-%p %#06x/%#06x %#09x\n",
/*
* Advance.
*/
"Empty region at %p.", uPtrWhere);
}
"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;
{
}
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.
*/
{
int rc;
/*
* First time around we have to allocate memory before we can get the image bits.
*/
{
return supHardNtVpSetInfo1(pErrInfo, VERR_SUP_VP_IMAGE_TOO_BIG, "Image %s is too large: %zu bytes (%#zx).",
return supHardNtVpSetInfo1(pErrInfo, VERR_SUP_VP_NO_MEMORY, "Failed to allocate %zu bytes for image %s.",
if (RT_FAILURE(rc))
return supHardNtVpSetInfo1(pErrInfo, VERR_SUP_VP_NO_MEMORY, "RTLdrGetBits failed on image %s: %Rrc",
}
/*
* 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
|| pfnGetImport)
{
pEntry->fValidBits = false;
if (RT_FAILURE(rc))
return supHardNtVpSetInfo1(pErrInfo, VERR_SUP_VP_NO_MEMORY, "RTLdrGetBits failed on image %s: %Rrc",
}
return VINF_SUCCESS;
}
/**
* Frees all resources associated with a cache entry and wipes the members
* clean.
*
* @param pEntry The entry to delete.
*/
{
{
}
{
}
{
}
{
}
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)
{
while (i-- > 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).
*/
{
/*
* 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.
*/
while (i-- > 0)
return &g_aSupNtVpLdrCacheEntries[i];
return NULL;
}
#endif /* IN_RING3 */
static int supHardNtLdrCacheNewEntry(PSUPHNTLDRCACHEENTRY pEntry, const char *pszName, PUNICODE_STRING pUniStrPath,
{
/*
* Open the image file.
*/
InitializeObjectAttributes(&ObjAttr, pUniStrPath, OBJ_CASE_INSENSITIVE, NULL /*hRootDir*/, NULL /*pSecDesc*/);
#ifdef IN_RING0
#endif
&ObjAttr,
&Ios,
NULL /* Allocation Size*/,
NULL /*EaBuffer*/,
0 /*EaLength*/);
if (NT_SUCCESS(rcNt))
if (!NT_SUCCESS(rcNt))
/*
* Figure out validation flags we'll be using and create the reader
* for this image.
*/
if (f32bitResourceDll)
if (RT_FAILURE(rc))
{
return rc;
}
/*
* Finally, open the image with the loader
*/
if (RT_FAILURE(rc))
/*
* Fill in the cache entry.
*/
pEntry->fValidBits = false;
#ifdef IN_SUP_HARDENED_R3
/*
* Log the image timestamp when in the hardened exe.
*/
uint64_t uTimestamp = 0;
#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.
*/
{
/*
* Locate the dll.
*/
uint32_t i = 0;
while ( i < RT_ELEMENTS(g_apszSupNtVpAllowedDlls)
i++;
if (i >= RT_ELEMENTS(g_apszSupNtVpAllowedDlls))
return VERR_FILE_NOT_FOUND;
/*
* Try the cache.
*/
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.
*/
return VERR_INTERNAL_ERROR_3;
int rc = supHardNtLdrCacheNewEntry(&g_aSupNtVpLdrCacheEntries[g_cSupNtVpLdrCacheEntries], pszName, &UniStr,
if (RT_SUCCESS(rc))
{
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.
*/
{
while (i-- > 0)
{
#ifdef IN_RING3
/*
* Try the cache first.
*/
if (pImage->pCacheEntry)
continue;
/*
* Not in the cache, so load it into the cache.
*/
#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.
*/
#endif
if (RT_FAILURE(rc))
return rc;
#ifdef IN_RING3
#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.
* @param hProcess The process to verify.
*/
{
/*
* Make sure there is exactly one executable image.
*/
unsigned cExecs = 0;
unsigned iExe = ~0U;
while (i-- > 0)
{
{
cExecs++;
iExe = i;
}
}
if (cExecs == 0)
"No executable mapping found in the virtual address space.");
if (cExecs != 1)
"Found more than one executable mapping in the virtual address space.");
/*
* Check that it matches the executable image of the process.
*/
int rc;
if (!pUniStr)
"Error allocating %zu bytes for process name.", cbUniStr);
NTSTATUS rcNt = NtQueryInformationProcess(hProcess, ProcessImageFileName, pUniStr, cbUniStr - sizeof(WCHAR), &cbIgn);
if (NT_SUCCESS(rcNt))
{
rc = VINF_SUCCESS;
else
{
"Process image name does not match the exectuable we found: %ls vs %ls.",
}
}
else
"NtQueryInformationProcess/ProcessImageFileName failed: %#x", rcNt);
if (RT_FAILURE(rc))
return rc;
/*
* Validate the signing of the executable image.
* This will load the fDllCharecteristics and fImageCharecteristics members we use below.
*/
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.
*/
rcNt = NtQueryInformationProcess(hProcess, ProcessImageInformation, &ImageInfo, sizeof(ImageInfo), NULL);
if (!NT_SUCCESS(rcNt))
{
if ( rcNt == STATUS_INVALID_PARAMETER
&& hProcess != NtCurrentProcess() )
return VINF_SUCCESS;
}
"EXE DllCharacteristics=%#x, expected IMAGE_DLLCHARACTERISTICS_FORCE_INTEGRITY to be set.",
"EXE DllCharacteristics=%#x, expected IMAGE_DLLCHARACTERISTICS_DYNAMIC_BASE to be set.",
"EXE DllCharacteristics=%#x, expected IMAGE_DLLCHARACTERISTICS_NX_COMPAT to be set.",
"EXE Info.DllCharacteristics=%#x fDllCharecteristics=%#x.",
"EXE Info.ImageCharacteristics=%#x fImageCharecteristics=%#x.",
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.
* @param hProcess The process to verify.
*/
{
/*
* Check for duplicate entries (paranoia).
*/
while (i-- > 1)
{
uint32_t j = i;
while (j-- > 0)
"Duplicate image entries for %s: %ls and %ls",
}
/*
* Check that both ntdll and kernel32 are present.
* ASSUMES the entries in g_apszSupNtVpAllowedDlls are all lower case.
*/
while (i-- > 0)
iNtDll = i;
iKernel32 = i;
if (iNtDll == UINT32_MAX)
"The process has no NTDLL.DLL.");
"The process has no KERNEL32.DLL.");
"The process already has KERNEL32.DLL loaded.");
/*
* Verify that the DLLs are correctly signed (by MS).
*/
while (i-- > 0)
{
if (RT_FAILURE(rc))
return rc;
}
return VINF_SUCCESS;
}
/**
* 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,
{
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 (RT_SUCCESS(rc))
if (RT_SUCCESS(rc))
{
/*
* Allocate and initialize memory for the state.
*/
if (pThis)
{
/*
* Perform the verification.
*/
if (RT_SUCCESS(rc))
if (RT_SUCCESS(rc))
if (RT_SUCCESS(rc))
if (pcFixes)
/*
* Clean up the state.
*/
#ifdef IN_RING0
#endif
}
else
"Failed to allocate %zu bytes for state structures.", sizeof(*pThis));
}
return rc;
}