diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-11 08:17:27 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-11 08:17:27 +0000 |
commit | f215e02bf85f68d3a6106c2a1f4f7f063f819064 (patch) | |
tree | 6bb5b92c046312c4e95ac2620b10ddf482d3fa8b /src/VBox/HostDrivers/Support/win | |
parent | Initial commit. (diff) | |
download | virtualbox-f215e02bf85f68d3a6106c2a1f4f7f063f819064.tar.xz virtualbox-f215e02bf85f68d3a6106c2a1f4f7f063f819064.zip |
Adding upstream version 7.0.14-dfsg.upstream/7.0.14-dfsg
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
19 files changed, 23267 insertions, 0 deletions
diff --git a/src/VBox/HostDrivers/Support/win/Makefile.kup b/src/VBox/HostDrivers/Support/win/Makefile.kup new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/src/VBox/HostDrivers/Support/win/Makefile.kup diff --git a/src/VBox/HostDrivers/Support/win/SUPDrv-win.cpp b/src/VBox/HostDrivers/Support/win/SUPDrv-win.cpp new file mode 100644 index 00000000..369e68c0 --- /dev/null +++ b/src/VBox/HostDrivers/Support/win/SUPDrv-win.cpp @@ -0,0 +1,5719 @@ +/* $Id: SUPDrv-win.cpp $ */ +/** @file + * VBoxDrv - The VirtualBox Support Driver - Windows NT specifics. + */ + +/* + * Copyright (C) 2006-2023 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 <https://www.gnu.org/licenses>. + * + * 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 * +*********************************************************************************************************************************/ +#ifndef IPRT_NT_MAP_TO_ZW +# define IPRT_NT_MAP_TO_ZW +#endif +#define LOG_GROUP LOG_GROUP_SUP_DRV +#include "../SUPDrvInternal.h" +#include <excpt.h> +#include <ntimage.h> + +#include <iprt/assert.h> +#include <iprt/avl.h> +#include <iprt/ctype.h> +#include <iprt/initterm.h> +#include <iprt/mem.h> +#include <iprt/process.h> +#include <iprt/power.h> +#include <iprt/rand.h> +#include <iprt/semaphore.h> +#include <iprt/spinlock.h> +#include <iprt/string.h> +#include <iprt/utf16.h> +#include <iprt/x86.h> +#include <VBox/log.h> +#include <VBox/err.h> + +#include <iprt/asm-amd64-x86.h> + +#ifdef VBOX_WITH_HARDENING +# include "SUPHardenedVerify-win.h" +#endif + + +/********************************************************************************************************************************* +* Defined Constants And Macros * +*********************************************************************************************************************************/ +/** The support service name. */ +#define SERVICE_NAME "VBoxDrv" +/** The Pool tag (VBox). */ +#define SUPDRV_NT_POOL_TAG 'xoBV' + +/** NT device name for user access. */ +#define DEVICE_NAME_NT_USR L"\\Device\\VBoxDrvU" +#ifdef VBOX_WITH_HARDENING +/** Macro for checking for deflecting calls to the stub device. */ +# define VBOXDRV_COMPLETE_IRP_AND_RETURN_IF_STUB_DEV(a_pDevObj, a_pIrp) \ + do { if ((a_pDevObj) == g_pDevObjStub) \ + return supdrvNtCompleteRequest(STATUS_ACCESS_DENIED, a_pIrp); \ + } while (0) +/** Macro for checking for deflecting calls to the stub and error info + * devices. */ +# define VBOXDRV_COMPLETE_IRP_AND_RETURN_IF_STUB_OR_ERROR_INFO_DEV(a_pDevObj, a_pIrp) \ + do { if ((a_pDevObj) == g_pDevObjStub || (a_pDevObj) == g_pDevObjErrorInfo) \ + return supdrvNtCompleteRequest(STATUS_ACCESS_DENIED, a_pIrp); \ + } while (0) +#else +# define VBOXDRV_COMPLETE_IRP_AND_RETURN_IF_STUB_DEV(a_pDevObj, a_pIrp) do {} while (0) +# define VBOXDRV_COMPLETE_IRP_AND_RETURN_IF_STUB_OR_ERROR_INFO_DEV(a_pDevObj, a_pIrp) do {} while (0) +#endif + +/** Enables the fast I/O control code path. */ +#define VBOXDRV_WITH_FAST_IO + +/** Enables generating UID from NT SIDs so the GMM can share free memory + * among VMs running as the same user. */ +#define VBOXDRV_WITH_SID_TO_UID_MAPPING + +/* Missing if we're compiling against older WDKs. */ +#ifndef NonPagedPoolNx +# define NonPagedPoolNx ((POOL_TYPE)512) +#endif + + +/********************************************************************************************************************************* +* Structures and Typedefs * +*********************************************************************************************************************************/ +#ifdef VBOXDRV_WITH_SID_TO_UID_MAPPING +/** + * SID to User ID mapping. + * + * This is used to generate a RTUID value for a NT security identifier. + * Normally, the UID is the hash of the SID string, but due to collisions it may + * differ. See g_NtUserIdHashTree and g_NtUserIdUidTree. + */ +typedef struct SUPDRVNTUSERID +{ + /** Hash tree node, key: RTStrHash1 of szSid. */ + AVLLU32NODECORE HashCore; + /** UID three node, key: The UID. */ + AVLU32NODECORE UidCore; + /** Reference counter. */ + uint32_t volatile cRefs; + /** The length of the SID string. */ + uint16_t cchSid; + /** The SID string for the user. */ + char szSid[RT_FLEXIBLE_ARRAY]; +} SUPDRVNTUSERID; +/** Pointer to a SID to UID mapping. */ +typedef SUPDRVNTUSERID *PSUPDRVNTUSERID; +#endif + +/** + * Device extension used by VBoxDrvU. + */ +typedef struct SUPDRVDEVEXTUSR +{ + /** Global cookie (same location as in SUPDRVDEVEXT, different value). */ + uint32_t u32Cookie; + /** Pointer to the main driver extension. */ + PSUPDRVDEVEXT pMainDrvExt; +} SUPDRVDEVEXTUSR; +AssertCompileMembersAtSameOffset(SUPDRVDEVEXT, u32Cookie, SUPDRVDEVEXTUSR, u32Cookie); +/** Pointer to the VBoxDrvU device extension. */ +typedef SUPDRVDEVEXTUSR *PSUPDRVDEVEXTUSR; +/** Value of SUPDRVDEVEXTUSR::u32Cookie. */ +#define SUPDRVDEVEXTUSR_COOKIE UINT32_C(0x12345678) + +/** Get the main device extension. */ +#define SUPDRVNT_GET_DEVEXT(pDevObj) \ + ( pDevObj != g_pDevObjUsr \ + ? (PSUPDRVDEVEXT)pDevObj->DeviceExtension \ + : ((PSUPDRVDEVEXTUSR)pDevObj->DeviceExtension)->pMainDrvExt ) + +#ifdef VBOX_WITH_HARDENING + +/** + * Device extension used by VBoxDrvStub. + */ +typedef struct SUPDRVDEVEXTSTUB +{ + /** Common header. */ + SUPDRVDEVEXTUSR Common; +} SUPDRVDEVEXTSTUB; +/** Pointer to the VBoxDrvStub device extension. */ +typedef SUPDRVDEVEXTSTUB *PSUPDRVDEVEXTSTUB; +/** Value of SUPDRVDEVEXTSTUB::Common.u32Cookie. */ +#define SUPDRVDEVEXTSTUB_COOKIE UINT32_C(0x90abcdef) + + +/** + * Device extension used by VBoxDrvErrorInfo. + */ +typedef struct SUPDRVDEVEXTERRORINFO +{ + /** Common header. */ + SUPDRVDEVEXTUSR Common; +} SUPDRVDEVEXTERRORINFO; +/** Pointer to the VBoxDrvErrorInfo device extension. */ +typedef SUPDRVDEVEXTERRORINFO *PSUPDRVDEVEXTERRORINFO; +/** Value of SUPDRVDEVEXTERRORINFO::Common.u32Cookie. */ +#define SUPDRVDEVEXTERRORINFO_COOKIE UINT32_C(0xBadC0ca0) + +/** + * Error info for a failed VBoxDrv or VBoxDrvStub open attempt. + */ +typedef struct SUPDRVNTERRORINFO +{ + /** The list entry (in g_ErrorInfoHead). */ + RTLISTNODE ListEntry; + /** The ID of the process this error info belongs to. */ + HANDLE hProcessId; + /** The ID of the thread owning this info. */ + HANDLE hThreadId; + /** Milliseconds createion timestamp (for cleaning up). */ + uint64_t uCreatedMsTs; + /** Number of bytes of valid info. */ + uint32_t cchErrorInfo; + /** The error info. */ + char szErrorInfo[16384 - sizeof(RTLISTNODE) - sizeof(HANDLE)*2 - sizeof(uint64_t) - sizeof(uint32_t) - 0x20]; +} SUPDRVNTERRORINFO; +/** Pointer to error info. */ +typedef SUPDRVNTERRORINFO *PSUPDRVNTERRORINFO; + + +/** + * The kind of process we're protecting. + */ +typedef enum SUPDRVNTPROTECTKIND +{ + kSupDrvNtProtectKind_Invalid = 0, + + /** Stub process protection while performing process verification. + * Next: StubSpawning (or free) */ + kSupDrvNtProtectKind_StubUnverified, + /** Stub process protection before it creates the VM process. + * Next: StubParent, StubDead. */ + kSupDrvNtProtectKind_StubSpawning, + /** Stub process protection while having a VM process as child. + * Next: StubDead */ + kSupDrvNtProtectKind_StubParent, + /** Dead stub process. */ + kSupDrvNtProtectKind_StubDead, + + /** Potential VM process. + * Next: VmProcessConfirmed, VmProcessDead. */ + kSupDrvNtProtectKind_VmProcessUnconfirmed, + /** Confirmed VM process. + * Next: VmProcessDead. */ + kSupDrvNtProtectKind_VmProcessConfirmed, + /** Dead VM process. */ + kSupDrvNtProtectKind_VmProcessDead, + + /** End of valid protection kinds. */ + kSupDrvNtProtectKind_End +} SUPDRVNTPROTECTKIND; + +/** + * A NT process protection structure. + */ +typedef struct SUPDRVNTPROTECT +{ + /** The AVL node core structure. The process ID is the pid. */ + AVLPVNODECORE AvlCore; + /** Magic value (SUPDRVNTPROTECT_MAGIC). */ + uint32_t volatile u32Magic; + /** Reference counter. */ + uint32_t volatile cRefs; + /** The kind of process we're protecting. */ + SUPDRVNTPROTECTKIND volatile enmProcessKind; + /** Whether this structure is in the tree. */ + bool fInTree : 1; + /** 7,: Hack to allow the supid themes service duplicate handle privileges to + * our process. */ + bool fThemesFirstProcessCreateHandle : 1; + /** Vista, 7 & 8: Hack to allow more rights to the handle returned by + * NtCreateUserProcess. Only applicable to VmProcessUnconfirmed. */ + bool fFirstProcessCreateHandle : 1; + /** Vista, 7 & 8: Hack to allow more rights to the handle returned by + * NtCreateUserProcess. Only applicable to VmProcessUnconfirmed. */ + bool fFirstThreadCreateHandle : 1; + /** 8.1: Hack to allow more rights to the handle returned by + * NtCreateUserProcess. Only applicable to VmProcessUnconfirmed. */ + bool fCsrssFirstProcessCreateHandle : 1; + /** Vista, 7 & 8: Hack to allow more rights to the handle duplicated by CSRSS + * during process creation. Only applicable to VmProcessUnconfirmed. On + * 32-bit systems we allow two as ZoneAlarm's system call hooks has been + * observed to do some seemingly unnecessary duplication work. */ + int32_t volatile cCsrssFirstProcessDuplicateHandle; + + /** The parent PID for VM processes, otherwise NULL. */ + HANDLE hParentPid; + /** The TID of the thread opening VBoxDrv or VBoxDrvStub, NULL if not opened. */ + HANDLE hOpenTid; + /** The PID of the CSRSS process associated with this process. */ + HANDLE hCsrssPid; + /** Pointer to the CSRSS process structure (referenced). */ + PEPROCESS pCsrssProcess; + /** State dependent data. */ + union + { + /** A stub process in the StubParent state will keep a reference to a child + * while it's in the VmProcessUnconfirmed state so that it can be cleaned up + * correctly if things doesn't work out. */ + struct SUPDRVNTPROTECT *pChild; + /** A process in the VmProcessUnconfirmed state will keep a weak + * reference to the parent's protection structure so it can clean up the pChild + * reference the parent has to it. */ + struct SUPDRVNTPROTECT *pParent; + } u; +} SUPDRVNTPROTECT; +/** Pointer to a NT process protection record. */ +typedef SUPDRVNTPROTECT *PSUPDRVNTPROTECT; +/** The SUPDRVNTPROTECT::u32Magic value (Robert A. Heinlein). */ +# define SUPDRVNTPROTECT_MAGIC UINT32_C(0x19070707) +/** The SUPDRVNTPROTECT::u32Magic value of a dead structure. */ +# define SUPDRVNTPROTECT_MAGIC_DEAD UINT32_C(0x19880508) + +/** Pointer to ObGetObjectType. */ +typedef POBJECT_TYPE (NTAPI *PFNOBGETOBJECTTYPE)(PVOID); +/** Pointer to ObRegisterCallbacks. */ +typedef NTSTATUS (NTAPI *PFNOBREGISTERCALLBACKS)(POB_CALLBACK_REGISTRATION, PVOID *); +/** Pointer to ObUnregisterCallbacks. */ +typedef VOID (NTAPI *PFNOBUNREGISTERCALLBACKS)(PVOID); +/** Pointer to PsSetCreateProcessNotifyRoutineEx. */ +typedef NTSTATUS (NTAPI *PFNPSSETCREATEPROCESSNOTIFYROUTINEEX)(PCREATE_PROCESS_NOTIFY_ROUTINE_EX, BOOLEAN); +/** Pointer to PsReferenceProcessFilePointer. */ +typedef NTSTATUS (NTAPI *PFNPSREFERENCEPROCESSFILEPOINTER)(PEPROCESS, PFILE_OBJECT *); +/** Pointer to PsIsProtectedProcessLight. */ +typedef BOOLEAN (NTAPI *PFNPSISPROTECTEDPROCESSLIGHT)(PEPROCESS); +/** Pointer to ZwAlpcCreatePort. */ +typedef NTSTATUS (NTAPI *PFNZWALPCCREATEPORT)(PHANDLE, POBJECT_ATTRIBUTES, struct _ALPC_PORT_ATTRIBUTES *); + +#endif /* VBOX_WITH_HARDENINIG */ + + +/********************************************************************************************************************************* +* Internal Functions * +*********************************************************************************************************************************/ +static void _stdcall VBoxDrvNtUnload(PDRIVER_OBJECT pDrvObj); +static NTSTATUS _stdcall VBoxDrvNtCreate(PDEVICE_OBJECT pDevObj, PIRP pIrp); +static NTSTATUS _stdcall VBoxDrvNtCleanup(PDEVICE_OBJECT pDevObj, PIRP pIrp); +static NTSTATUS _stdcall VBoxDrvNtClose(PDEVICE_OBJECT pDevObj, PIRP pIrp); +#ifdef VBOXDRV_WITH_FAST_IO +static BOOLEAN _stdcall VBoxDrvNtFastIoDeviceControl(PFILE_OBJECT pFileObj, BOOLEAN fWait, PVOID pvInput, ULONG cbInput, + PVOID pvOutput, ULONG cbOutput, ULONG uCmd, + PIO_STATUS_BLOCK pIoStatus, PDEVICE_OBJECT pDevObj); +#endif +static NTSTATUS _stdcall VBoxDrvNtDeviceControl(PDEVICE_OBJECT pDevObj, PIRP pIrp); +static int VBoxDrvNtDeviceControlSlow(PSUPDRVDEVEXT pDevExt, PSUPDRVSESSION pSession, PIRP pIrp, PIO_STACK_LOCATION pStack); +static NTSTATUS _stdcall VBoxDrvNtInternalDeviceControl(PDEVICE_OBJECT pDevObj, PIRP pIrp); +static VOID _stdcall VBoxPowerDispatchCallback(PVOID pCallbackContext, PVOID pArgument1, PVOID pArgument2); +static NTSTATUS _stdcall VBoxDrvNtRead(PDEVICE_OBJECT pDevObj, PIRP pIrp); +static NTSTATUS _stdcall VBoxDrvNtNotSupportedStub(PDEVICE_OBJECT pDevObj, PIRP pIrp); +static NTSTATUS VBoxDrvNtErr2NtStatus(int rc); +#ifdef VBOX_WITH_HARDENING +static NTSTATUS supdrvNtProtectInit(void); +static void supdrvNtProtectTerm(void); +static int supdrvNtProtectCreate(PSUPDRVNTPROTECT *ppNtProtect, HANDLE hPid, + SUPDRVNTPROTECTKIND enmProcessKind, bool fLink); +static void supdrvNtProtectRelease(PSUPDRVNTPROTECT pNtProtect); +static PSUPDRVNTPROTECT supdrvNtProtectLookup(HANDLE hPid); +static int supdrvNtProtectFindAssociatedCsrss(PSUPDRVNTPROTECT pNtProtect); +static int supdrvNtProtectVerifyProcess(PSUPDRVNTPROTECT pNtProtect); + +static bool supdrvNtIsDebuggerAttached(void); +static void supdrvNtErrorInfoCleanupProcess(HANDLE hProcessId); + +#endif + + +/********************************************************************************************************************************* +* Exported Functions * +*********************************************************************************************************************************/ +RT_C_DECLS_BEGIN +NTSTATUS _stdcall DriverEntry(PDRIVER_OBJECT pDrvObj, PUNICODE_STRING pRegPath); +RT_C_DECLS_END + + +/********************************************************************************************************************************* +* Global Variables * +*********************************************************************************************************************************/ +/** The non-paged pool type to use, NonPagedPool or NonPagedPoolNx. */ +static POOL_TYPE g_enmNonPagedPoolType = NonPagedPool; +/** Pointer to the system device instance. */ +static PDEVICE_OBJECT g_pDevObjSys = NULL; +/** Pointer to the user device instance. */ +static PDEVICE_OBJECT g_pDevObjUsr = NULL; +#ifdef VBOXDRV_WITH_FAST_IO +/** Fast I/O dispatch table. */ +static FAST_IO_DISPATCH const g_VBoxDrvFastIoDispatch = +{ + /* .SizeOfFastIoDispatch = */ sizeof(g_VBoxDrvFastIoDispatch), + /* .FastIoCheckIfPossible = */ NULL, + /* .FastIoRead = */ NULL, + /* .FastIoWrite = */ NULL, + /* .FastIoQueryBasicInfo = */ NULL, + /* .FastIoQueryStandardInfo = */ NULL, + /* .FastIoLock = */ NULL, + /* .FastIoUnlockSingle = */ NULL, + /* .FastIoUnlockAll = */ NULL, + /* .FastIoUnlockAllByKey = */ NULL, + /* .FastIoDeviceControl = */ VBoxDrvNtFastIoDeviceControl, + /* .AcquireFileForNtCreateSection = */ NULL, + /* .ReleaseFileForNtCreateSection = */ NULL, + /* .FastIoDetachDevice = */ NULL, + /* .FastIoQueryNetworkOpenInfo = */ NULL, + /* .AcquireForModWrite = */ NULL, + /* .MdlRead = */ NULL, + /* .MdlReadComplete = */ NULL, + /* .PrepareMdlWrite = */ NULL, + /* .MdlWriteComplete = */ NULL, + /* .FastIoReadCompressed = */ NULL, + /* .FastIoWriteCompressed = */ NULL, + /* .MdlReadCompleteCompressed = */ NULL, + /* .MdlWriteCompleteCompressed = */ NULL, + /* .FastIoQueryOpen = */ NULL, + /* .ReleaseForModWrite = */ NULL, + /* .AcquireForCcFlush = */ NULL, + /* .ReleaseForCcFlush = */ NULL, +}; +#endif /* VBOXDRV_WITH_FAST_IO */ + +/** Default ZERO value. */ +static ULONG g_fOptDefaultZero = 0; +/** Registry values. + * We wrap these in a struct to ensure they are followed by a little zero + * padding in order to limit the chance of trouble on unpatched systems. */ +struct +{ + /** The ForceAsync registry value. */ + ULONG fOptForceAsyncTsc; + /** Padding. */ + uint64_t au64Padding[2]; +} g_Options = { FALSE, 0, 0 }; +/** Registry query table for RtlQueryRegistryValues. */ +static RTL_QUERY_REGISTRY_TABLE g_aRegValues[] = +{ + { + /* .QueryRoutine = */ NULL, + /* .Flags = */ RTL_QUERY_REGISTRY_DIRECT | RTL_QUERY_REGISTRY_TYPECHECK, + /* .Name = */ L"ForceAsyncTsc", + /* .EntryContext = */ &g_Options.fOptForceAsyncTsc, + /* .DefaultType = */ (REG_DWORD << RTL_QUERY_REGISTRY_TYPECHECK_SHIFT) | REG_DWORD, + /* .DefaultData = */ &g_fOptDefaultZero, + /* .DefaultLength = */ sizeof(g_fOptDefaultZero), + }, + { NULL, 0, NULL, NULL, 0, NULL, 0 } /* terminator entry. */ +}; + +/** Pointer to KeQueryMaximumGroupCount. */ +static PFNKEQUERYMAXIMUMGROUPCOUNT g_pfnKeQueryMaximumGroupCount = NULL; +/** Pointer to KeGetProcessorIndexFromNumber. */ +static PFNKEGETPROCESSORINDEXFROMNUMBER g_pfnKeGetProcessorIndexFromNumber = NULL; +/** Pointer to KeGetProcessorNumberFromIndex. */ +static PFNKEGETPROCESSORNUMBERFROMINDEX g_pfnKeGetProcessorNumberFromIndex = NULL; + +#ifdef VBOXDRV_WITH_SID_TO_UID_MAPPING +/** Spinlock protecting g_NtUserIdHashTree and g_NtUserIdUidTree. */ +static RTSPINLOCK g_hNtUserIdLock = NIL_RTSPINLOCK; +/** AVL tree of SUPDRVNTUSERID structures by hash value. */ +static PAVLLU32NODECORE g_NtUserIdHashTree = NULL; +/** AVL tree of SUPDRVNTUSERID structures by UID. */ +static PAVLU32NODECORE g_NtUserIdUidTree = NULL; +#endif + +#ifdef VBOX_WITH_HARDENING +/** Pointer to the stub device instance. */ +static PDEVICE_OBJECT g_pDevObjStub = NULL; +/** Spinlock protecting g_NtProtectTree as well as the releasing of protection + * structures. */ +static RTSPINLOCK g_hNtProtectLock = NIL_RTSPINLOCK; +/** AVL tree of SUPDRVNTPROTECT structures. */ +static AVLPVTREE g_NtProtectTree = NULL; +/** Cookie returned by ObRegisterCallbacks for the callbacks. */ +static PVOID g_pvObCallbacksCookie = NULL; +/** Combined windows NT version number. See SUP_MAKE_NT_VER_COMBINED. */ +uint32_t g_uNtVerCombined = 0; +/** Pointer to ObGetObjectType if available.. */ +static PFNOBGETOBJECTTYPE g_pfnObGetObjectType = NULL; +/** Pointer to ObRegisterCallbacks if available.. */ +static PFNOBREGISTERCALLBACKS g_pfnObRegisterCallbacks = NULL; +/** Pointer to ObUnregisterCallbacks if available.. */ +static PFNOBUNREGISTERCALLBACKS g_pfnObUnRegisterCallbacks = NULL; +/** Pointer to PsSetCreateProcessNotifyRoutineEx if available.. */ +static PFNPSSETCREATEPROCESSNOTIFYROUTINEEX g_pfnPsSetCreateProcessNotifyRoutineEx = NULL; +/** Pointer to PsReferenceProcessFilePointer if available. */ +static PFNPSREFERENCEPROCESSFILEPOINTER g_pfnPsReferenceProcessFilePointer = NULL; +/** Pointer to PsIsProtectedProcessLight. */ +static PFNPSISPROTECTEDPROCESSLIGHT g_pfnPsIsProtectedProcessLight = NULL; +/** Pointer to ZwAlpcCreatePort. */ +static PFNZWALPCCREATEPORT g_pfnZwAlpcCreatePort = NULL; + +# ifdef RT_ARCH_AMD64 +extern "C" { +/** Pointer to KiServiceLinkage (used to fake missing ZwQueryVirtualMemory on + * XP64 / W2K3-64). */ +PFNRT g_pfnKiServiceLinkage = NULL; +/** Pointer to KiServiceInternal (used to fake missing ZwQueryVirtualMemory on + * XP64 / W2K3-64) */ +PFNRT g_pfnKiServiceInternal = NULL; +} +# endif +/** The primary ALPC port object type. (LpcPortObjectType at init time.) */ +static POBJECT_TYPE g_pAlpcPortObjectType1 = NULL; +/** The secondary ALPC port object type. (Sampled at runtime.) */ +static POBJECT_TYPE volatile g_pAlpcPortObjectType2 = NULL; + +/** Pointer to the error information device instance. */ +static PDEVICE_OBJECT g_pDevObjErrorInfo = NULL; +/** Fast mutex semaphore protecting the error info list. */ +static RTSEMMUTEX g_hErrorInfoLock = NIL_RTSEMMUTEX; +/** Head of the error info (SUPDRVNTERRORINFO). */ +static RTLISTANCHOR g_ErrorInfoHead; + +#endif + + +/** + * Takes care of creating the devices and their symbolic links. + * + * @returns NT status code. + * @param pDrvObj Pointer to driver object. + */ +static NTSTATUS vboxdrvNtCreateDevices(PDRIVER_OBJECT pDrvObj) +{ + /* + * System device. + */ + UNICODE_STRING DevName; + RtlInitUnicodeString(&DevName, SUPDRV_NT_DEVICE_NAME_SYS); + NTSTATUS rcNt = IoCreateDevice(pDrvObj, sizeof(SUPDRVDEVEXT), &DevName, FILE_DEVICE_UNKNOWN, 0, FALSE, &g_pDevObjSys); + if (NT_SUCCESS(rcNt)) + { + /* + * User device. + */ + RtlInitUnicodeString(&DevName, SUPDRV_NT_DEVICE_NAME_USR); + rcNt = IoCreateDevice(pDrvObj, sizeof(SUPDRVDEVEXTUSR), &DevName, FILE_DEVICE_UNKNOWN, 0, FALSE, &g_pDevObjUsr); + if (NT_SUCCESS(rcNt)) + { + PSUPDRVDEVEXTUSR pDevExtUsr = (PSUPDRVDEVEXTUSR)g_pDevObjUsr->DeviceExtension; + pDevExtUsr->pMainDrvExt = (PSUPDRVDEVEXT)g_pDevObjSys->DeviceExtension; + pDevExtUsr->u32Cookie = SUPDRVDEVEXTUSR_COOKIE; + +#ifdef VBOX_WITH_HARDENING + /* + * Hardened stub device. + */ + RtlInitUnicodeString(&DevName, SUPDRV_NT_DEVICE_NAME_STUB); + rcNt = IoCreateDevice(pDrvObj, sizeof(SUPDRVDEVEXTSTUB), &DevName, FILE_DEVICE_UNKNOWN, 0, FALSE, &g_pDevObjStub); + if (NT_SUCCESS(rcNt)) + { + if (NT_SUCCESS(rcNt)) + { + PSUPDRVDEVEXTSTUB pDevExtStub = (PSUPDRVDEVEXTSTUB)g_pDevObjStub->DeviceExtension; + pDevExtStub->Common.pMainDrvExt = (PSUPDRVDEVEXT)g_pDevObjSys->DeviceExtension; + pDevExtStub->Common.u32Cookie = SUPDRVDEVEXTSTUB_COOKIE; + + /* + * Hardened error information device. + */ + RtlInitUnicodeString(&DevName, SUPDRV_NT_DEVICE_NAME_ERROR_INFO); + rcNt = IoCreateDevice(pDrvObj, sizeof(SUPDRVDEVEXTERRORINFO), &DevName, FILE_DEVICE_UNKNOWN, 0, FALSE, + &g_pDevObjErrorInfo); + if (NT_SUCCESS(rcNt)) + { + g_pDevObjErrorInfo->Flags |= DO_BUFFERED_IO; + + if (NT_SUCCESS(rcNt)) + { + PSUPDRVDEVEXTERRORINFO pDevExtErrInf = (PSUPDRVDEVEXTERRORINFO)g_pDevObjStub->DeviceExtension; + pDevExtErrInf->Common.pMainDrvExt = (PSUPDRVDEVEXT)g_pDevObjSys->DeviceExtension; + pDevExtErrInf->Common.u32Cookie = SUPDRVDEVEXTERRORINFO_COOKIE; + +#endif + /* Done. */ + return rcNt; +#ifdef VBOX_WITH_HARDENING + } + + /* Bail out. */ + IoDeleteDevice(g_pDevObjErrorInfo); + g_pDevObjErrorInfo = NULL; + } + } + + /* Bail out. */ + IoDeleteDevice(g_pDevObjStub); + g_pDevObjUsr = NULL; + } + IoDeleteDevice(g_pDevObjUsr); + g_pDevObjUsr = NULL; +#endif + } + IoDeleteDevice(g_pDevObjSys); + g_pDevObjSys = NULL; + } + return rcNt; +} + +/** + * Destroys the devices and links created by vboxdrvNtCreateDevices. + */ +static void vboxdrvNtDestroyDevices(void) +{ + if (g_pDevObjUsr) + { + PSUPDRVDEVEXTUSR pDevExtUsr = (PSUPDRVDEVEXTUSR)g_pDevObjUsr->DeviceExtension; + pDevExtUsr->pMainDrvExt = NULL; + } +#ifdef VBOX_WITH_HARDENING + if (g_pDevObjStub) + { + PSUPDRVDEVEXTSTUB pDevExtStub = (PSUPDRVDEVEXTSTUB)g_pDevObjStub->DeviceExtension; + pDevExtStub->Common.pMainDrvExt = NULL; + } + if (g_pDevObjErrorInfo) + { + PSUPDRVDEVEXTERRORINFO pDevExtErrorInfo = (PSUPDRVDEVEXTERRORINFO)g_pDevObjStub->DeviceExtension; + pDevExtErrorInfo->Common.pMainDrvExt = NULL; + } +#endif + +#ifdef VBOX_WITH_HARDENING + IoDeleteDevice(g_pDevObjErrorInfo); + g_pDevObjErrorInfo = NULL; + IoDeleteDevice(g_pDevObjStub); + g_pDevObjStub = NULL; +#endif + IoDeleteDevice(g_pDevObjUsr); + g_pDevObjUsr = NULL; + IoDeleteDevice(g_pDevObjSys); + g_pDevObjSys = NULL; +} + + +/** + * Driver entry point. + * + * @returns appropriate status code. + * @param pDrvObj Pointer to driver object. + * @param pRegPath Registry base path. + */ +NTSTATUS _stdcall DriverEntry(PDRIVER_OBJECT pDrvObj, PUNICODE_STRING pRegPath) +{ + RT_NOREF1(pRegPath); + + /* + * Sanity checks. + */ +#ifdef VBOXDRV_WITH_FAST_IO + if (g_VBoxDrvFastIoDispatch.FastIoDeviceControl != VBoxDrvNtFastIoDeviceControl) + { + DbgPrint("VBoxDrv: FastIoDeviceControl=%p instead of %p\n", + g_VBoxDrvFastIoDispatch.FastIoDeviceControl, VBoxDrvNtFastIoDeviceControl); + return STATUS_INTERNAL_ERROR; + } +#endif + + /* + * Figure out if we can use NonPagedPoolNx or not. + */ + ULONG ulMajorVersion, ulMinorVersion, ulBuildNumber; + PsGetVersion(&ulMajorVersion, &ulMinorVersion, &ulBuildNumber, NULL); + if (ulMajorVersion > 6 || (ulMajorVersion == 6 && ulMinorVersion >= 2)) /* >= 6.2 (W8)*/ + g_enmNonPagedPoolType = NonPagedPoolNx; + + /* + * Query options first so any overflows on unpatched machines will do less + * harm (see MS11-011 / 2393802 / 2011-03-18). + * + * Unfortunately, pRegPath isn't documented as zero terminated, even if it + * quite likely always is, so we have to make a copy here. + */ + NTSTATUS rcNt; + PWSTR pwszCopy = (PWSTR)ExAllocatePoolWithTag(g_enmNonPagedPoolType, pRegPath->Length + sizeof(WCHAR), 'VBox'); + if (pwszCopy) + { + memcpy(pwszCopy, pRegPath->Buffer, pRegPath->Length); + pwszCopy[pRegPath->Length / sizeof(WCHAR)] = '\0'; + rcNt = RtlQueryRegistryValues(RTL_REGISTRY_ABSOLUTE | RTL_REGISTRY_OPTIONAL, pwszCopy, + g_aRegValues, NULL /*pvContext*/, NULL /*pvEnv*/); + ExFreePoolWithTag(pwszCopy, 'VBox'); + /* Probably safe to ignore rcNt here. */ + } + + /* + * Resolve methods we want but isn't available everywhere. + */ + UNICODE_STRING RoutineName; + RtlInitUnicodeString(&RoutineName, L"KeQueryMaximumGroupCount"); + g_pfnKeQueryMaximumGroupCount = (PFNKEQUERYMAXIMUMGROUPCOUNT)MmGetSystemRoutineAddress(&RoutineName); + + RtlInitUnicodeString(&RoutineName, L"KeGetProcessorIndexFromNumber"); + g_pfnKeGetProcessorIndexFromNumber = (PFNKEGETPROCESSORINDEXFROMNUMBER)MmGetSystemRoutineAddress(&RoutineName); + + RtlInitUnicodeString(&RoutineName, L"KeGetProcessorNumberFromIndex"); + g_pfnKeGetProcessorNumberFromIndex = (PFNKEGETPROCESSORNUMBERFROMINDEX)MmGetSystemRoutineAddress(&RoutineName); + + Assert( (g_pfnKeGetProcessorNumberFromIndex != NULL) == (g_pfnKeGetProcessorIndexFromNumber != NULL) + && (g_pfnKeGetProcessorNumberFromIndex != NULL) == (g_pfnKeQueryMaximumGroupCount != NULL)); /* all or nothing. */ + + /* + * Initialize the runtime (IPRT). + */ + int vrc = RTR0Init(0); + if (RT_SUCCESS(vrc)) + { + Log(("VBoxDrv::DriverEntry\n")); + +#ifdef VBOX_WITH_HARDENING + /* + * Initialize process protection. + */ + rcNt = supdrvNtProtectInit(); + if (NT_SUCCESS(rcNt)) +#endif + { +#ifdef VBOXDRV_WITH_SID_TO_UID_MAPPING + /* + * Create the spinlock for the SID -> UID mappings. + */ + vrc = RTSpinlockCreate(&g_hNtUserIdLock, RTSPINLOCK_FLAGS_INTERRUPT_UNSAFE, "NtUserId"); + if (RT_SUCCESS(vrc)) +#endif + { + /* + * Create device. + * (That means creating a device object and a symbolic link so the DOS + * subsystems (OS/2, win32, ++) can access the device.) + */ + rcNt = vboxdrvNtCreateDevices(pDrvObj); + if (NT_SUCCESS(rcNt)) + { + /* + * Initialize the device extension. + */ + PSUPDRVDEVEXT pDevExt = (PSUPDRVDEVEXT)g_pDevObjSys->DeviceExtension; + memset(pDevExt, 0, sizeof(*pDevExt)); + + vrc = supdrvInitDevExt(pDevExt, sizeof(SUPDRVSESSION)); + if (!vrc) + { + /* + * Setup the driver entry points in pDrvObj. + */ + pDrvObj->DriverUnload = VBoxDrvNtUnload; + pDrvObj->MajorFunction[IRP_MJ_CREATE] = VBoxDrvNtCreate; + pDrvObj->MajorFunction[IRP_MJ_CLEANUP] = VBoxDrvNtCleanup; + pDrvObj->MajorFunction[IRP_MJ_CLOSE] = VBoxDrvNtClose; + pDrvObj->MajorFunction[IRP_MJ_DEVICE_CONTROL] = VBoxDrvNtDeviceControl; + pDrvObj->MajorFunction[IRP_MJ_INTERNAL_DEVICE_CONTROL] = VBoxDrvNtInternalDeviceControl; + pDrvObj->MajorFunction[IRP_MJ_READ] = VBoxDrvNtRead; + pDrvObj->MajorFunction[IRP_MJ_WRITE] = VBoxDrvNtNotSupportedStub; + +#ifdef VBOXDRV_WITH_FAST_IO + /* Fast I/O to speed up guest execution roundtrips. */ + pDrvObj->FastIoDispatch = (PFAST_IO_DISPATCH)&g_VBoxDrvFastIoDispatch; +#endif + + /* + * Register ourselves for power state changes. We don't + * currently care if this fails. + */ + UNICODE_STRING CallbackName; + RtlInitUnicodeString(&CallbackName, L"\\Callback\\PowerState"); + + OBJECT_ATTRIBUTES Attr; + InitializeObjectAttributes(&Attr, &CallbackName, OBJ_CASE_INSENSITIVE, NULL, NULL); + + rcNt = ExCreateCallback(&pDevExt->pObjPowerCallback, &Attr, TRUE, TRUE); + if (rcNt == STATUS_SUCCESS) + pDevExt->hPowerCallback = ExRegisterCallback(pDevExt->pObjPowerCallback, + VBoxPowerDispatchCallback, + g_pDevObjSys); + + /* + * Done! Returning success! + */ + Log(("VBoxDrv::DriverEntry returning STATUS_SUCCESS\n")); + return STATUS_SUCCESS; + } + + /* + * Failed. Clean up. + */ + Log(("supdrvInitDevExit failed with vrc=%d!\n", vrc)); + rcNt = VBoxDrvNtErr2NtStatus(vrc); + + vboxdrvNtDestroyDevices(); + } +#ifdef VBOXDRV_WITH_SID_TO_UID_MAPPING + RTSpinlockDestroy(g_hNtUserIdLock); + g_hNtUserIdLock = NIL_RTSPINLOCK; +#endif + } +#ifdef VBOXDRV_WITH_SID_TO_UID_MAPPING + else + rcNt = VBoxDrvNtErr2NtStatus(vrc); +#endif +#ifdef VBOX_WITH_HARDENING + supdrvNtProtectTerm(); +#endif + } + RTTermRunCallbacks(RTTERMREASON_UNLOAD, 0); + RTR0Term(); + } + else + { + Log(("RTR0Init failed with vrc=%d!\n", vrc)); + rcNt = VBoxDrvNtErr2NtStatus(vrc); + } + if (NT_SUCCESS(rcNt)) + rcNt = STATUS_INVALID_PARAMETER; + return rcNt; +} + + +/** + * Unload the driver. + * + * @param pDrvObj Driver object. + */ +void _stdcall VBoxDrvNtUnload(PDRIVER_OBJECT pDrvObj) +{ + PSUPDRVDEVEXT pDevExt = (PSUPDRVDEVEXT)g_pDevObjSys->DeviceExtension; + + Log(("VBoxDrvNtUnload at irql %d\n", KeGetCurrentIrql())); + + /* Clean up the power callback registration. */ + if (pDevExt->hPowerCallback) + ExUnregisterCallback(pDevExt->hPowerCallback); + if (pDevExt->pObjPowerCallback) + ObDereferenceObject(pDevExt->pObjPowerCallback); + + /* + * We ASSUME that it's not possible to unload a driver with open handles. + */ + supdrvDeleteDevExt(pDevExt); +#ifdef VBOXDRV_WITH_SID_TO_UID_MAPPING + RTSpinlockDestroy(g_hNtUserIdLock); + g_hNtUserIdLock = NIL_RTSPINLOCK; +#endif +#ifdef VBOX_WITH_HARDENING + supdrvNtProtectTerm(); +#endif + RTTermRunCallbacks(RTTERMREASON_UNLOAD, 0); + RTR0Term(); + vboxdrvNtDestroyDevices(); + + NOREF(pDrvObj); +} + +#ifdef VBOXDRV_WITH_SID_TO_UID_MAPPING + +/** + * Worker for supdrvNtUserIdMakeForSession. + */ +static bool supdrvNtUserIdMakeUid(PSUPDRVNTUSERID pNtUserId) +{ + pNtUserId->UidCore.Key = pNtUserId->HashCore.Key; + for (uint32_t cTries = 0; cTries < _4K; cTries++) + { + bool fRc = RTAvlU32Insert(&g_NtUserIdUidTree, &pNtUserId->UidCore); + if (fRc) + return true; + pNtUserId->UidCore.Key += pNtUserId->cchSid | 1; + } + return false; +} + + +/** + * Try create a RTUID value for the session. + * + * @returns VBox status code. + * @param pSession The session to try set SUPDRVSESSION::Uid for. + */ +static int supdrvNtUserIdMakeForSession(PSUPDRVSESSION pSession) +{ + /* + * Get the current security context and query the User SID for it. + */ + SECURITY_SUBJECT_CONTEXT Ctx = { NULL, SecurityIdentification, NULL, NULL }; + SeCaptureSubjectContext(&Ctx); + + int rc; + TOKEN_USER *pTokenUser = NULL; + NTSTATUS rcNt = SeQueryInformationToken(SeQuerySubjectContextToken(&Ctx) /* or always PrimaryToken?*/, + TokenUser, (PVOID *)&pTokenUser); + if (NT_SUCCESS(rcNt)) + { + /* + * Convert the user SID to a string to make it easier to handle, then prepare + * a user ID entry for it as that way we can combine lookup and insertion and + * avoid needing to deal with races. + */ + UNICODE_STRING UniStr = RTNT_NULL_UNISTR(); + rcNt = RtlConvertSidToUnicodeString(&UniStr, pTokenUser->User.Sid, TRUE /*AllocateDesitnationString*/); + if (NT_SUCCESS(rcNt)) + { + size_t cchSid = 0; + rc = RTUtf16CalcUtf8LenEx(UniStr.Buffer, UniStr.Length / sizeof(RTUTF16), &cchSid); + if (RT_SUCCESS(rc)) + { + PSUPDRVNTUSERID const pNtUserIdNew = (PSUPDRVNTUSERID)RTMemAlloc(RT_UOFFSETOF_DYN(SUPDRVNTUSERID, szSid[cchSid + 1])); + if (pNtUserIdNew) + { + char *pszSid = pNtUserIdNew->szSid; + rc = RTUtf16ToUtf8Ex(UniStr.Buffer, UniStr.Length / sizeof(RTUTF16), &pszSid, cchSid + 1, NULL); + if (RT_SUCCESS(rc)) + { + pNtUserIdNew->HashCore.Key = RTStrHash1(pNtUserIdNew->szSid); + pNtUserIdNew->cchSid = (uint16_t)cchSid; + pNtUserIdNew->cRefs = 1; + Log5Func(("pNtUserId=%p cchSid=%u hash=%#x '%s'\n", pNtUserIdNew, cchSid, pNtUserIdNew->HashCore.Key, pszSid)); + + /* + * Do the lookup / insert. + */ + RTSpinlockAcquire(g_hNtUserIdLock); + AssertCompileMemberOffset(SUPDRVNTUSERID, HashCore, 0); + PSUPDRVNTUSERID pNtUserId = (PSUPDRVNTUSERID)RTAvllU32Get(&g_NtUserIdHashTree, pNtUserIdNew->HashCore.Key); + if (pNtUserId) + { + /* Match the strings till we reach the end of the collision list. */ + PSUPDRVNTUSERID const pNtUserIdHead = pNtUserId; + while ( pNtUserId + && ( pNtUserId->cchSid != cchSid + || memcmp(pNtUserId->szSid, pNtUserId->szSid, cchSid) != 0)) + pNtUserId = (PSUPDRVNTUSERID)pNtUserId->HashCore.pList; + if (pNtUserId) + { + /* Found matching: Retain reference and free the new entry we prepared. */ + uint32_t const cRefs = ASMAtomicIncU32(&pNtUserId->cRefs); + Assert(cRefs < _16K); RT_NOREF(cRefs); + RTSpinlockRelease(g_hNtUserIdLock); + Log5Func(("Using %p / %#x instead\n", pNtUserId, pNtUserId->UidCore.Key)); + } + else + { + /* No match: Try insert prepared entry after the head node. */ + if (supdrvNtUserIdMakeUid(pNtUserIdNew)) + { + pNtUserIdNew->HashCore.pList = pNtUserIdHead->HashCore.pList; + pNtUserIdHead->HashCore.pList = &pNtUserIdNew->HashCore; + pNtUserId = pNtUserIdNew; + } + RTSpinlockRelease(g_hNtUserIdLock); + if (pNtUserId) + Log5Func(("Using %p / %#x (the prepared one)\n", pNtUserId, pNtUserId->UidCore.Key)); + else + LogRelFunc(("supdrvNtUserIdMakeForSession: failed to insert new\n")); + } + } + else + { + /* No matching hash: Try insert the prepared entry. */ + pNtUserIdNew->UidCore.Key = pNtUserIdNew->HashCore.Key; + if (supdrvNtUserIdMakeUid(pNtUserIdNew)) + { + RTAvllU32Insert(&g_NtUserIdHashTree, &pNtUserIdNew->HashCore); + pNtUserId = pNtUserIdNew; + } + RTSpinlockRelease(g_hNtUserIdLock); + if (pNtUserId) + Log5Func(("Using %p / %#x (the prepared one, no conflict)\n", pNtUserId, pNtUserId->UidCore.Key)); + else + LogRelFunc(("failed to insert!! WTF!?!\n")); + } + + if (pNtUserId != pNtUserIdNew) + RTMemFree(pNtUserIdNew); + + /* + * Update the session info. + */ + pSession->pNtUserId = pNtUserId; + pSession->Uid = pNtUserId ? (RTUID)pNtUserId->UidCore.Key : NIL_RTUID; + } + else + RTMemFree(pNtUserIdNew); + } + else + rc = VERR_NO_MEMORY; + } + RtlFreeUnicodeString(&UniStr); + } + else + { + rc = RTErrConvertFromNtStatus(rcNt); + LogFunc(("RtlConvertSidToUnicodeString failed: %#x / %Rrc\n", rcNt, rc)); + } + ExFreePool(pTokenUser); + } + else + { + rc = RTErrConvertFromNtStatus(rcNt); + LogFunc(("SeQueryInformationToken failed: %#x / %Rrc\n", rcNt, rc)); + } + + SeReleaseSubjectContext(&Ctx); + return rc; +} + + +/** + * Releases a reference to @a pNtUserId. + * + * @param pNtUserId The NT user ID entry to release. + */ +static void supdrvNtUserIdRelease(PSUPDRVNTUSERID pNtUserId) +{ + if (pNtUserId) + { + uint32_t const cRefs = ASMAtomicDecU32(&pNtUserId->cRefs); + Log5Func(("%p / %#x: cRefs=%d\n", pNtUserId, pNtUserId->cRefs)); + Assert(cRefs < _8K); + if (cRefs == 0) + { + RTSpinlockAcquire(g_hNtUserIdLock); + if (pNtUserId->cRefs == 0) + { + PAVLLU32NODECORE pAssert1 = RTAvllU32RemoveNode(&g_NtUserIdHashTree, &pNtUserId->HashCore); + PAVLU32NODECORE pAssert2 = RTAvlU32Remove(&g_NtUserIdUidTree, pNtUserId->UidCore.Key); + + RTSpinlockRelease(g_hNtUserIdLock); + + Assert(pAssert1 == &pNtUserId->HashCore); + Assert(pAssert2 == &pNtUserId->UidCore); + RT_NOREF(pAssert1, pAssert2); + + RTMemFree(pNtUserId); + } + else + RTSpinlockRelease(g_hNtUserIdLock); + } + } +} + +#endif /* VBOXDRV_WITH_SID_TO_UID_MAPPING */ + +/** + * For simplifying request completion into a simple return statement, extended + * version. + * + * @returns rcNt + * @param rcNt The status code. + * @param uInfo Extra info value. + * @param pIrp The IRP. + */ +DECLINLINE(NTSTATUS) supdrvNtCompleteRequestEx(NTSTATUS rcNt, ULONG_PTR uInfo, PIRP pIrp) +{ + pIrp->IoStatus.Status = rcNt; + pIrp->IoStatus.Information = uInfo; + IoCompleteRequest(pIrp, IO_NO_INCREMENT); + return rcNt; +} + + +/** + * For simplifying request completion into a simple return statement. + * + * @returns rcNt + * @param rcNt The status code. + * @param pIrp The IRP. + */ +DECLINLINE(NTSTATUS) supdrvNtCompleteRequest(NTSTATUS rcNt, PIRP pIrp) +{ + return supdrvNtCompleteRequestEx(rcNt, 0 /*uInfo*/, pIrp); +} + + +/** + * Create (i.e. Open) file entry point. + * + * @param pDevObj Device object. + * @param pIrp Request packet. + */ +NTSTATUS _stdcall VBoxDrvNtCreate(PDEVICE_OBJECT pDevObj, PIRP pIrp) +{ + Log(("VBoxDrvNtCreate: RequestorMode=%d\n", pIrp->RequestorMode)); + PIO_STACK_LOCATION pStack = IoGetCurrentIrpStackLocation(pIrp); + PFILE_OBJECT pFileObj = pStack->FileObject; + PSUPDRVDEVEXT pDevExt = SUPDRVNT_GET_DEVEXT(pDevObj); + + /* + * We are not remotely similar to a directory... + * (But this is possible.) + */ + if (pStack->Parameters.Create.Options & FILE_DIRECTORY_FILE) + return supdrvNtCompleteRequest(STATUS_NOT_A_DIRECTORY, pIrp); + + /* + * Don't create a session for kernel clients, they'll close the handle + * immediately and work with the file object via + * VBoxDrvNtInternalDeviceControl. The first request will be one to + * create a session. + */ + NTSTATUS rcNt; + if (pIrp->RequestorMode == KernelMode) + { + if (pDevObj == g_pDevObjSys) + return supdrvNtCompleteRequestEx(STATUS_SUCCESS, FILE_OPENED, pIrp); + + rcNt = STATUS_ACCESS_DENIED; + } +#ifdef VBOX_WITH_HARDENING + /* + * Anyone can open the error device. + */ + else if (pDevObj == g_pDevObjErrorInfo) + { + pFileObj->FsContext = NULL; + return supdrvNtCompleteRequestEx(STATUS_SUCCESS, FILE_OPENED, pIrp); + } +#endif + else + { +#if defined(VBOX_WITH_HARDENING) && !defined(VBOX_WITHOUT_DEBUGGER_CHECKS) + /* + * Make sure no debuggers are attached to non-user processes. + */ + if ( pDevObj != g_pDevObjUsr + && supdrvNtIsDebuggerAttached()) + { + LogRel(("vboxdrv: Process %p is being debugged, access to vboxdrv / vboxdrvu declined.\n", + PsGetProcessId(PsGetCurrentProcess()))); + rcNt = STATUS_TRUST_FAILURE; + } + else +#endif + { + int rc = VINF_SUCCESS; + +#ifdef VBOX_WITH_HARDENING + /* + * Access to the stub device is only granted to processes which + * passes verification. + * + * Note! The stub device has no need for a SUPDRVSESSION structure, + * so the it uses the SUPDRVNTPROTECT directly instead. + */ + if (pDevObj == g_pDevObjStub) + { + PSUPDRVNTPROTECT pNtProtect = NULL; + rc = supdrvNtProtectCreate(&pNtProtect, PsGetProcessId(PsGetCurrentProcess()), + kSupDrvNtProtectKind_StubUnverified, true /*fLink*/); + if (RT_SUCCESS(rc)) + { + rc = supdrvNtProtectFindAssociatedCsrss(pNtProtect); + if (RT_SUCCESS(rc)) + rc = supdrvNtProtectVerifyProcess(pNtProtect); + if (RT_SUCCESS(rc)) + { + pFileObj->FsContext = pNtProtect; /* Keeps reference. */ + return supdrvNtCompleteRequestEx(STATUS_SUCCESS, FILE_OPENED, pIrp); + } + + supdrvNtProtectRelease(pNtProtect); + } + LogRel(("vboxdrv: Declined %p access to VBoxDrvStub: rc=%d\n", PsGetProcessId(PsGetCurrentProcess()), rc)); + } + /* + * Unrestricted access is only granted to a process in the + * VmProcessUnconfirmed state that checks out correctly and is + * allowed to transition to VmProcessConfirmed. Again, only one + * session per process. + */ + else if (pDevObj != g_pDevObjUsr) + { + PSUPDRVNTPROTECT pNtProtect = supdrvNtProtectLookup(PsGetProcessId(PsGetCurrentProcess())); + if (pNtProtect) + { + if (pNtProtect->enmProcessKind == kSupDrvNtProtectKind_VmProcessUnconfirmed) + { + rc = supdrvNtProtectVerifyProcess(pNtProtect); + if (RT_SUCCESS(rc)) + { + /* Create a session. */ + PSUPDRVSESSION pSession; + rc = supdrvCreateSession(pDevExt, true /*fUser*/, pDevObj == g_pDevObjSys /*fUnrestricted*/, + &pSession); + if (RT_SUCCESS(rc)) + { +#ifdef VBOXDRV_WITH_SID_TO_UID_MAPPING + rc = supdrvNtUserIdMakeForSession(pSession); + if (RT_SUCCESS(rc)) +#endif + rc = supdrvSessionHashTabInsert(pDevExt, pSession, (PSUPDRVSESSION *)&pFileObj->FsContext, NULL); + supdrvSessionRelease(pSession); + if (RT_SUCCESS(rc)) + { + pSession->pNtProtect = pNtProtect; /* Keeps reference. */ + return supdrvNtCompleteRequestEx(STATUS_SUCCESS, FILE_OPENED, pIrp); + } + } + + /* No second attempt. */ + RTSpinlockAcquire(g_hNtProtectLock); + if (pNtProtect->enmProcessKind == kSupDrvNtProtectKind_VmProcessConfirmed) + pNtProtect->enmProcessKind = kSupDrvNtProtectKind_VmProcessDead; + RTSpinlockRelease(g_hNtProtectLock); + + LogRel(("vboxdrv: supdrvCreateSession failed for process %p: rc=%d.\n", + PsGetProcessId(PsGetCurrentProcess()), rc)); + } + else + LogRel(("vboxdrv: Process %p failed process verification: rc=%d.\n", + PsGetProcessId(PsGetCurrentProcess()), rc)); + } + else + { + LogRel(("vboxdrv: %p is not a budding VM process (enmProcessKind=%d).\n", + PsGetProcessId(PsGetCurrentProcess()), pNtProtect->enmProcessKind)); + rc = VERR_SUPDRV_NOT_BUDDING_VM_PROCESS_2; + } + supdrvNtProtectRelease(pNtProtect); + } + else + { + LogRel(("vboxdrv: %p is not a budding VM process.\n", PsGetProcessId(PsGetCurrentProcess()))); + rc = VERR_SUPDRV_NOT_BUDDING_VM_PROCESS_1; + } + } + /* + * Call common code to create an unprivileged session. + */ + else + { + PSUPDRVSESSION pSession; + rc = supdrvCreateSession(pDevExt, true /*fUser*/, false /*fUnrestricted*/, &pSession); + if (RT_SUCCESS(rc)) + { +#ifdef VBOXDRV_WITH_SID_TO_UID_MAPPING + rc = supdrvNtUserIdMakeForSession(pSession); + if (RT_SUCCESS(rc)) +#endif + rc = supdrvSessionHashTabInsert(pDevExt, pSession, (PSUPDRVSESSION *)&pFileObj->FsContext, NULL); + supdrvSessionRelease(pSession); + if (RT_SUCCESS(rc)) + { + pFileObj->FsContext = pSession; /* Keeps reference. No race. */ + pSession->pNtProtect = NULL; + return supdrvNtCompleteRequestEx(STATUS_SUCCESS, FILE_OPENED, pIrp); + } + } + } + +#else /* !VBOX_WITH_HARDENING */ + /* + * Call common code to create a session. + */ + pFileObj->FsContext = NULL; + PSUPDRVSESSION pSession; + rc = supdrvCreateSession(pDevExt, true /*fUser*/, pDevObj == g_pDevObjSys /*fUnrestricted*/, &pSession); + if (RT_SUCCESS(rc)) + { +# ifdef VBOXDRV_WITH_SID_TO_UID_MAPPING + rc = supdrvNtUserIdMakeForSession(pSession); + if (RT_SUCCESS(rc)) +# endif + rc = supdrvSessionHashTabInsert(pDevExt, pSession, (PSUPDRVSESSION *)&pFileObj->FsContext, NULL); + supdrvSessionRelease(pSession); + if (RT_SUCCESS(rc)) + return supdrvNtCompleteRequestEx(STATUS_SUCCESS, FILE_OPENED, pIrp); + + } +#endif /* !VBOX_WITH_HARDENING */ + + /* bail out */ + rcNt = VBoxDrvNtErr2NtStatus(rc); + } + } + + Assert(!NT_SUCCESS(rcNt)); + pFileObj->FsContext = NULL; + return supdrvNtCompleteRequest(rcNt, pIrp); /* Note. the IoStatus is completely ignored on error. */ +} + + +/** + * Clean up file handle entry point. + * + * This is called when the last handle reference is released, or something like + * that. In the case of IoGetDeviceObjectPointer, this is called as it closes + * the handle, however it will go on using the file object afterwards... + * + * @param pDevObj Device object. + * @param pIrp Request packet. + */ +NTSTATUS _stdcall VBoxDrvNtCleanup(PDEVICE_OBJECT pDevObj, PIRP pIrp) +{ + PSUPDRVDEVEXT pDevExt = SUPDRVNT_GET_DEVEXT(pDevObj); + PIO_STACK_LOCATION pStack = IoGetCurrentIrpStackLocation(pIrp); + PFILE_OBJECT pFileObj = pStack->FileObject; + +#ifdef VBOX_WITH_HARDENING + if (pDevObj == g_pDevObjStub) + { + PSUPDRVNTPROTECT pNtProtect = (PSUPDRVNTPROTECT)pFileObj->FsContext; + Log(("VBoxDrvNtCleanup: pDevExt=%p pFileObj=%p pNtProtect=%p\n", pDevExt, pFileObj, pNtProtect)); + if (pNtProtect) + { + supdrvNtProtectRelease(pNtProtect); + pFileObj->FsContext = NULL; + } + } + else if (pDevObj == g_pDevObjErrorInfo) + supdrvNtErrorInfoCleanupProcess(PsGetCurrentProcessId()); + else +#endif + { + PSUPDRVSESSION pSession = supdrvSessionHashTabLookup(pDevExt, RTProcSelf(), RTR0ProcHandleSelf(), + (PSUPDRVSESSION *)&pFileObj->FsContext); + Log(("VBoxDrvNtCleanup: pDevExt=%p pFileObj=%p pSession=%p\n", pDevExt, pFileObj, pSession)); + if (pSession) + { + supdrvSessionHashTabRemove(pDevExt, pSession, NULL); + supdrvSessionRelease(pSession); /* Drops the reference from supdrvSessionHashTabLookup. */ + } + } + + return supdrvNtCompleteRequest(STATUS_SUCCESS, pIrp); +} + + +/** + * Close file entry point. + * + * @param pDevObj Device object. + * @param pIrp Request packet. + */ +NTSTATUS _stdcall VBoxDrvNtClose(PDEVICE_OBJECT pDevObj, PIRP pIrp) +{ + PSUPDRVDEVEXT pDevExt = SUPDRVNT_GET_DEVEXT(pDevObj); + PIO_STACK_LOCATION pStack = IoGetCurrentIrpStackLocation(pIrp); + PFILE_OBJECT pFileObj = pStack->FileObject; + +#ifdef VBOX_WITH_HARDENING + if (pDevObj == g_pDevObjStub) + { + PSUPDRVNTPROTECT pNtProtect = (PSUPDRVNTPROTECT)pFileObj->FsContext; + Log(("VBoxDrvNtClose: pDevExt=%p pFileObj=%p pNtProtect=%p\n", pDevExt, pFileObj, pNtProtect)); + if (pNtProtect) + { + supdrvNtProtectRelease(pNtProtect); + pFileObj->FsContext = NULL; + } + } + else if (pDevObj == g_pDevObjErrorInfo) + supdrvNtErrorInfoCleanupProcess(PsGetCurrentProcessId()); + else +#endif + { + PSUPDRVSESSION pSession = supdrvSessionHashTabLookup(pDevExt, RTProcSelf(), RTR0ProcHandleSelf(), + (PSUPDRVSESSION *)&pFileObj->FsContext); + Log(("VBoxDrvNtCleanup: pDevExt=%p pFileObj=%p pSession=%p\n", pDevExt, pFileObj, pSession)); + if (pSession) + { + supdrvSessionHashTabRemove(pDevExt, pSession, NULL); + supdrvSessionRelease(pSession); /* Drops the reference from supdrvSessionHashTabLookup. */ + } + } + + return supdrvNtCompleteRequest(STATUS_SUCCESS, pIrp); +} + + +#ifdef VBOXDRV_WITH_FAST_IO +/** + * Fast I/O device control callback. + * + * This performs no buffering, neither on the way in or out. + * + * @returns TRUE if handled, FALSE if the normal I/O control routine should be + * called. + * @param pFileObj The file object. + * @param fWait Whether it's a blocking call + * @param pvInput The input buffer as specified by the user. + * @param cbInput The size of the input buffer. + * @param pvOutput The output buffer as specfied by the user. + * @param cbOutput The size of the output buffer. + * @param uCmd The I/O command/function being invoked. + * @param pIoStatus Where to return the status of the operation. + * @param pDevObj The device object.. + */ +static BOOLEAN _stdcall VBoxDrvNtFastIoDeviceControl(PFILE_OBJECT pFileObj, BOOLEAN fWait, PVOID pvInput, ULONG cbInput, + PVOID pvOutput, ULONG cbOutput, ULONG uCmd, + PIO_STATUS_BLOCK pIoStatus, PDEVICE_OBJECT pDevObj) +{ + RT_NOREF1(fWait); + + /* + * Only the normal devices, not the stub or error info ones. + */ + if (pDevObj != g_pDevObjSys && pDevObj != g_pDevObjUsr) + { + pIoStatus->Status = STATUS_NOT_SUPPORTED; + pIoStatus->Information = 0; + return TRUE; + } + + /* + * Check the input a little bit and get a the session references. + */ + PSUPDRVDEVEXT pDevExt = SUPDRVNT_GET_DEVEXT(pDevObj); + PSUPDRVSESSION pSession = supdrvSessionHashTabLookup(pDevExt, RTProcSelf(), RTR0ProcHandleSelf(), + (PSUPDRVSESSION *)&pFileObj->FsContext); + if (!pSession) + { + pIoStatus->Status = STATUS_TRUST_FAILURE; + pIoStatus->Information = 0; + return TRUE; + } + + if (pSession->fUnrestricted) + { +#if defined(VBOX_WITH_HARDENING) && !defined(VBOX_WITHOUT_DEBUGGER_CHECKS) + if (supdrvNtIsDebuggerAttached()) + { + pIoStatus->Status = STATUS_TRUST_FAILURE; + pIoStatus->Information = 0; + supdrvSessionRelease(pSession); + return TRUE; + } +#endif + + /* + * Deal with the 2-3 high-speed IOCtl that takes their arguments from + * the session and iCmd, and does not return anything. + */ + if ( (uCmd & 3) == METHOD_NEITHER + && (uint32_t)((uCmd - SUP_IOCTL_FAST_DO_FIRST) >> 2) < (uint32_t)32) + { + int rc = supdrvIOCtlFast((uCmd - SUP_IOCTL_FAST_DO_FIRST) >> 2, + (unsigned)(uintptr_t)pvOutput/* VMCPU id */, + pDevExt, pSession); + pIoStatus->Status = RT_SUCCESS(rc) ? STATUS_SUCCESS : STATUS_INVALID_PARAMETER; + pIoStatus->Information = 0; /* Could be used to pass rc if we liked. */ + supdrvSessionRelease(pSession); + return TRUE; + } + } + + /* + * The normal path. + */ + NTSTATUS rcNt; + unsigned cbOut = 0; + int rc = 0; + Log2(("VBoxDrvNtFastIoDeviceControl(%p): ioctl=%#x pvIn=%p cbIn=%#x pvOut=%p cbOut=%#x pSession=%p\n", + pDevExt, uCmd, pvInput, cbInput, pvOutput, cbOutput, pSession)); + +# ifdef RT_ARCH_AMD64 + /* Don't allow 32-bit processes to do any I/O controls. */ + if (!IoIs32bitProcess(NULL)) +# endif + { + /* + * In this fast I/O device control path we have to do our own buffering. + */ + /* Verify that the I/O control function matches our pattern. */ + if ((uCmd & 0x3) == METHOD_BUFFERED) + { + /* Get the header so we can validate it a little bit against the + parameters before allocating any memory kernel for the reqest. */ + SUPREQHDR Hdr; + if (cbInput >= sizeof(Hdr) && cbOutput >= sizeof(Hdr)) + { + __try + { + RtlCopyMemory(&Hdr, pvInput, sizeof(Hdr)); + rcNt = STATUS_SUCCESS; + } + __except(EXCEPTION_EXECUTE_HANDLER) + { + rcNt = GetExceptionCode(); + Hdr.cbIn = Hdr.cbOut = 0; /* shut up MSC */ + } + } + else + { + Hdr.cbIn = Hdr.cbOut = 0; /* shut up MSC */ + rcNt = STATUS_INVALID_PARAMETER; + } + if (NT_SUCCESS(rcNt)) + { + /* Verify that the sizes in the request header are correct. */ + ULONG cbBuf = RT_MAX(cbInput, cbOutput); + if ( cbInput == Hdr.cbIn + && cbOutput == Hdr.cbOut + && cbBuf < _1M*16) + { + /* Allocate a buffer and copy all the input into it. */ + PSUPREQHDR pHdr = (PSUPREQHDR)ExAllocatePoolWithTag(g_enmNonPagedPoolType, cbBuf, 'VBox'); + if (pHdr) + { + __try + { + RtlCopyMemory(pHdr, pvInput, cbInput); + if (cbInput < cbBuf) + RtlZeroMemory((uint8_t *)pHdr + cbInput, cbBuf - cbInput); + if (!memcmp(pHdr, &Hdr, sizeof(Hdr))) + rcNt = STATUS_SUCCESS; + else + rcNt = STATUS_INVALID_PARAMETER; + } + __except(EXCEPTION_EXECUTE_HANDLER) + { + rcNt = GetExceptionCode(); + } + if (NT_SUCCESS(rcNt)) + { + /* + * Now call the common code to do the real work. + */ + rc = supdrvIOCtl(uCmd, pDevExt, pSession, pHdr, cbBuf); + if (RT_SUCCESS(rc)) + { + /* + * Copy back the result. + */ + cbOut = pHdr->cbOut; + if (cbOut > cbOutput) + { + cbOut = cbOutput; + OSDBGPRINT(("VBoxDrvNtFastIoDeviceControl: too much output! %#x > %#x; uCmd=%#x!\n", + pHdr->cbOut, cbOut, uCmd)); + } + if (cbOut) + { + __try + { + RtlCopyMemory(pvOutput, pHdr, cbOut); + rcNt = STATUS_SUCCESS; + } + __except(EXCEPTION_EXECUTE_HANDLER) + { + rcNt = GetExceptionCode(); + } + } + else + rcNt = STATUS_SUCCESS; + } + else if (rc == VERR_INVALID_PARAMETER) + rcNt = STATUS_INVALID_PARAMETER; + else + rcNt = STATUS_NOT_SUPPORTED; + Log2(("VBoxDrvNtFastIoDeviceControl: returns %#x cbOut=%d rc=%#x\n", rcNt, cbOut, rc)); + } + else + Log(("VBoxDrvNtFastIoDeviceControl: Error reading %u bytes of user memory at %p (uCmd=%#x)\n", + cbInput, pvInput, uCmd)); + ExFreePoolWithTag(pHdr, 'VBox'); + } + else + rcNt = STATUS_NO_MEMORY; + } + else + { + Log(("VBoxDrvNtFastIoDeviceControl: Mismatching sizes (%#x) - Hdr=%#lx/%#lx Irp=%#lx/%#lx!\n", + uCmd, Hdr.cbIn, Hdr.cbOut, cbInput, cbOutput)); + rcNt = STATUS_INVALID_PARAMETER; + } + } + } + else + { + Log(("VBoxDrvNtFastIoDeviceControl: not buffered request (%#x) - not supported\n", uCmd)); + rcNt = STATUS_NOT_SUPPORTED; + } + } +# ifdef RT_ARCH_AMD64 + else + { + Log(("VBoxDrvNtFastIoDeviceControl: WOW64 req - not supported\n")); + rcNt = STATUS_NOT_SUPPORTED; + } +# endif + + /* complete the request. */ + pIoStatus->Status = rcNt; + pIoStatus->Information = cbOut; + supdrvSessionRelease(pSession); + return TRUE; /* handled. */ +} +#endif /* VBOXDRV_WITH_FAST_IO */ + + +/** + * Device I/O Control entry point. + * + * @param pDevObj Device object. + * @param pIrp Request packet. + */ +NTSTATUS _stdcall VBoxDrvNtDeviceControl(PDEVICE_OBJECT pDevObj, PIRP pIrp) +{ + VBOXDRV_COMPLETE_IRP_AND_RETURN_IF_STUB_OR_ERROR_INFO_DEV(pDevObj, pIrp); + + PSUPDRVDEVEXT pDevExt = SUPDRVNT_GET_DEVEXT(pDevObj); + PIO_STACK_LOCATION pStack = IoGetCurrentIrpStackLocation(pIrp); + PSUPDRVSESSION pSession = supdrvSessionHashTabLookup(pDevExt, RTProcSelf(), RTR0ProcHandleSelf(), + (PSUPDRVSESSION *)&pStack->FileObject->FsContext); + + if (!RT_VALID_PTR(pSession)) + return supdrvNtCompleteRequest(STATUS_TRUST_FAILURE, pIrp); + + /* + * Deal with the 2-3 high-speed IOCtl that takes their arguments from + * the session and iCmd, and does not return anything. + */ + if (pSession->fUnrestricted) + { +#if defined(VBOX_WITH_HARDENING) && !defined(VBOX_WITHOUT_DEBUGGER_CHECKS) + if (supdrvNtIsDebuggerAttached()) + { + supdrvSessionRelease(pSession); + return supdrvNtCompleteRequest(STATUS_TRUST_FAILURE, pIrp); + } +#endif + + ULONG uCmd = pStack->Parameters.DeviceIoControl.IoControlCode; + if ( (uCmd & 3) == METHOD_NEITHER + && (uint32_t)((uCmd - SUP_IOCTL_FAST_DO_FIRST) >> 2) < (uint32_t)32) + { + int rc = supdrvIOCtlFast((uCmd - SUP_IOCTL_FAST_DO_FIRST) >> 2, + (unsigned)(uintptr_t)pIrp->UserBuffer /* VMCPU id */, + pDevExt, pSession); + + /* Complete the I/O request. */ + supdrvSessionRelease(pSession); + return supdrvNtCompleteRequest(RT_SUCCESS(rc) ? STATUS_SUCCESS : STATUS_INVALID_PARAMETER, pIrp); + } + } + + return VBoxDrvNtDeviceControlSlow(pDevExt, pSession, pIrp, pStack); +} + + +/** + * Worker for VBoxDrvNtDeviceControl that takes the slow IOCtl functions. + * + * @returns NT status code. + * + * @param pDevExt Device extension. + * @param pSession The session. + * @param pIrp Request packet. + * @param pStack The stack location containing the DeviceControl parameters. + */ +static int VBoxDrvNtDeviceControlSlow(PSUPDRVDEVEXT pDevExt, PSUPDRVSESSION pSession, PIRP pIrp, PIO_STACK_LOCATION pStack) +{ + NTSTATUS rcNt; + uint32_t cbOut = 0; + int rc = 0; + Log2(("VBoxDrvNtDeviceControlSlow(%p,%p): ioctl=%#x pBuf=%p cbIn=%#x cbOut=%#x pSession=%p\n", + pDevExt, pIrp, pStack->Parameters.DeviceIoControl.IoControlCode, + pIrp->AssociatedIrp.SystemBuffer, pStack->Parameters.DeviceIoControl.InputBufferLength, + pStack->Parameters.DeviceIoControl.OutputBufferLength, pSession)); + +#ifdef RT_ARCH_AMD64 + /* Don't allow 32-bit processes to do any I/O controls. */ + if (!IoIs32bitProcess(pIrp)) +#endif + { + /* Verify that it's a buffered CTL. */ + if ((pStack->Parameters.DeviceIoControl.IoControlCode & 0x3) == METHOD_BUFFERED) + { + /* Verify that the sizes in the request header are correct. */ + PSUPREQHDR pHdr = (PSUPREQHDR)pIrp->AssociatedIrp.SystemBuffer; + if ( pStack->Parameters.DeviceIoControl.InputBufferLength >= sizeof(*pHdr) + && pStack->Parameters.DeviceIoControl.InputBufferLength == pHdr->cbIn + && pStack->Parameters.DeviceIoControl.OutputBufferLength == pHdr->cbOut) + { + /* Zero extra output bytes to make sure we don't leak anything. */ + if (pHdr->cbIn < pHdr->cbOut) + RtlZeroMemory((uint8_t *)pHdr + pHdr->cbIn, pHdr->cbOut - pHdr->cbIn); + + /* + * Do the job. + */ + rc = supdrvIOCtl(pStack->Parameters.DeviceIoControl.IoControlCode, pDevExt, pSession, pHdr, + RT_MAX(pHdr->cbIn, pHdr->cbOut)); + if (!rc) + { + rcNt = STATUS_SUCCESS; + cbOut = pHdr->cbOut; + if (cbOut > pStack->Parameters.DeviceIoControl.OutputBufferLength) + { + cbOut = pStack->Parameters.DeviceIoControl.OutputBufferLength; + OSDBGPRINT(("VBoxDrvNtDeviceControlSlow: too much output! %#x > %#x; uCmd=%#x!\n", + pHdr->cbOut, cbOut, pStack->Parameters.DeviceIoControl.IoControlCode)); + } + } + else + rcNt = STATUS_INVALID_PARAMETER; + Log2(("VBoxDrvNtDeviceControlSlow: returns %#x cbOut=%d rc=%#x\n", rcNt, cbOut, rc)); + } + else + { + Log(("VBoxDrvNtDeviceControlSlow: Mismatching sizes (%#x) - Hdr=%#lx/%#lx Irp=%#lx/%#lx!\n", + pStack->Parameters.DeviceIoControl.IoControlCode, + pStack->Parameters.DeviceIoControl.InputBufferLength >= sizeof(*pHdr) ? pHdr->cbIn : 0, + pStack->Parameters.DeviceIoControl.InputBufferLength >= sizeof(*pHdr) ? pHdr->cbOut : 0, + pStack->Parameters.DeviceIoControl.InputBufferLength, + pStack->Parameters.DeviceIoControl.OutputBufferLength)); + rcNt = STATUS_INVALID_PARAMETER; + } + } + else + { + Log(("VBoxDrvNtDeviceControlSlow: not buffered request (%#x) - not supported\n", + pStack->Parameters.DeviceIoControl.IoControlCode)); + rcNt = STATUS_NOT_SUPPORTED; + } + } +#ifdef RT_ARCH_AMD64 + else + { + Log(("VBoxDrvNtDeviceControlSlow: WOW64 req - not supported\n")); + rcNt = STATUS_NOT_SUPPORTED; + } +#endif + + /* complete the request. */ + pIrp->IoStatus.Status = rcNt; + pIrp->IoStatus.Information = cbOut; + supdrvSessionRelease(pSession); + IoCompleteRequest(pIrp, IO_NO_INCREMENT); + return rcNt; +} + + +/** + * Internal Device I/O Control entry point, used for IDC. + * + * @param pDevObj Device object. + * @param pIrp Request packet. + */ +NTSTATUS _stdcall VBoxDrvNtInternalDeviceControl(PDEVICE_OBJECT pDevObj, PIRP pIrp) +{ + VBOXDRV_COMPLETE_IRP_AND_RETURN_IF_STUB_OR_ERROR_INFO_DEV(pDevObj, pIrp); + + PSUPDRVDEVEXT pDevExt = SUPDRVNT_GET_DEVEXT(pDevObj); + PIO_STACK_LOCATION pStack = IoGetCurrentIrpStackLocation(pIrp); + PFILE_OBJECT pFileObj = pStack ? pStack->FileObject : NULL; + PSUPDRVSESSION pSession = pFileObj ? (PSUPDRVSESSION)pFileObj->FsContext : NULL; + NTSTATUS rcNt; + unsigned cbOut = 0; + int rc = 0; + Log2(("VBoxDrvNtInternalDeviceControl(%p,%p): ioctl=%#x pBuf=%p cbIn=%#x cbOut=%#x pSession=%p\n", + pDevExt, pIrp, pStack->Parameters.DeviceIoControl.IoControlCode, + pIrp->AssociatedIrp.SystemBuffer, pStack->Parameters.DeviceIoControl.InputBufferLength, + pStack->Parameters.DeviceIoControl.OutputBufferLength, pSession)); + + /* Verify that it's a buffered CTL. */ + if ((pStack->Parameters.DeviceIoControl.IoControlCode & 0x3) == METHOD_BUFFERED) + { + /* Verify the pDevExt in the session. */ + if ( pStack->Parameters.DeviceIoControl.IoControlCode != SUPDRV_IDC_REQ_CONNECT + ? RT_VALID_PTR(pSession) && pSession->pDevExt == pDevExt + : !pSession + ) + { + /* Verify that the size in the request header is correct. */ + PSUPDRVIDCREQHDR pHdr = (PSUPDRVIDCREQHDR)pIrp->AssociatedIrp.SystemBuffer; + if ( pStack->Parameters.DeviceIoControl.InputBufferLength >= sizeof(*pHdr) + && pStack->Parameters.DeviceIoControl.InputBufferLength == pHdr->cb + && pStack->Parameters.DeviceIoControl.OutputBufferLength == pHdr->cb) + { + /* + * Call the generic code. + * + * Note! Connect and disconnect requires some extra attention + * in order to get the session handling right. + */ + if (pStack->Parameters.DeviceIoControl.IoControlCode == SUPDRV_IDC_REQ_DISCONNECT) + pFileObj->FsContext = NULL; + + rc = supdrvIDC(pStack->Parameters.DeviceIoControl.IoControlCode, pDevExt, pSession, pHdr); + if (!rc) + { + if (pStack->Parameters.DeviceIoControl.IoControlCode == SUPDRV_IDC_REQ_CONNECT) + pFileObj->FsContext = ((PSUPDRVIDCREQCONNECT)pHdr)->u.Out.pSession; + + rcNt = STATUS_SUCCESS; + cbOut = pHdr->cb; + } + else + { + rcNt = STATUS_INVALID_PARAMETER; + if (pStack->Parameters.DeviceIoControl.IoControlCode == SUPDRV_IDC_REQ_DISCONNECT) + pFileObj->FsContext = pSession; + } + Log2(("VBoxDrvNtInternalDeviceControl: returns %#x/rc=%#x\n", rcNt, rc)); + } + else + { + Log(("VBoxDrvNtInternalDeviceControl: Mismatching sizes (%#x) - Hdr=%#lx Irp=%#lx/%#lx!\n", + pStack->Parameters.DeviceIoControl.IoControlCode, + pStack->Parameters.DeviceIoControl.InputBufferLength >= sizeof(*pHdr) ? pHdr->cb : 0, + pStack->Parameters.DeviceIoControl.InputBufferLength, + pStack->Parameters.DeviceIoControl.OutputBufferLength)); + rcNt = STATUS_INVALID_PARAMETER; + } + } + else + rcNt = STATUS_NOT_SUPPORTED; + } + else + { + Log(("VBoxDrvNtInternalDeviceControl: not buffered request (%#x) - not supported\n", + pStack->Parameters.DeviceIoControl.IoControlCode)); + rcNt = STATUS_NOT_SUPPORTED; + } + + /* complete the request. */ + pIrp->IoStatus.Status = rcNt; + pIrp->IoStatus.Information = cbOut; + IoCompleteRequest(pIrp, IO_NO_INCREMENT); + return rcNt; +} + + +/** + * Implementation of the read major function for VBoxDrvErrorInfo. + * + * This is a stub function for the other devices. + * + * @returns NT status code. + * @param pDevObj The device object. + * @param pIrp The I/O request packet. + */ +NTSTATUS _stdcall VBoxDrvNtRead(PDEVICE_OBJECT pDevObj, PIRP pIrp) +{ + Log(("VBoxDrvNtRead\n")); + RT_NOREF1(pDevObj); + + NTSTATUS rcNt; + pIrp->IoStatus.Information = 0; + +#ifdef VBOX_WITH_HARDENING + /* + * VBoxDrvErrorInfo? + */ + if (pDevObj == g_pDevObjErrorInfo) + { + PIO_STACK_LOCATION pStack = IoGetCurrentIrpStackLocation(pIrp); + if ( pStack + && (pIrp->Flags & IRP_BUFFERED_IO)) + { + /* + * Look up the process error information. + */ + HANDLE hCurThreadId = PsGetCurrentThreadId(); + HANDLE hCurProcessId = PsGetCurrentProcessId(); + int rc = RTSemMutexRequestNoResume(g_hErrorInfoLock, RT_INDEFINITE_WAIT); + if (RT_SUCCESS(rc)) + { + PSUPDRVNTERRORINFO pMatch = NULL; + PSUPDRVNTERRORINFO pCur; + RTListForEach(&g_ErrorInfoHead, pCur, SUPDRVNTERRORINFO, ListEntry) + { + if ( pCur->hProcessId == hCurProcessId + && pCur->hThreadId == hCurThreadId) + { + pMatch = pCur; + break; + } + } + + /* + * Did we find error info and is the caller requesting data within it? + * If so, check the destination buffer and copy the data into it. + */ + if ( pMatch + && pStack->Parameters.Read.ByteOffset.QuadPart < pMatch->cchErrorInfo + && pStack->Parameters.Read.ByteOffset.QuadPart >= 0) + { + PVOID pvDstBuf = pIrp->AssociatedIrp.SystemBuffer; + if (pvDstBuf) + { + uint32_t offRead = (uint32_t)pStack->Parameters.Read.ByteOffset.QuadPart; + uint32_t cbToRead = pMatch->cchErrorInfo - offRead; + if (cbToRead < pStack->Parameters.Read.Length) + RT_BZERO((uint8_t *)pvDstBuf + cbToRead, pStack->Parameters.Read.Length - cbToRead); + else + cbToRead = pStack->Parameters.Read.Length; + memcpy(pvDstBuf, &pMatch->szErrorInfo[offRead], cbToRead); + pIrp->IoStatus.Information = cbToRead; + + rcNt = STATUS_SUCCESS; + } + else + rcNt = STATUS_INVALID_ADDRESS; + } + /* + * End of file. Free the info. + */ + else if (pMatch) + { + RTListNodeRemove(&pMatch->ListEntry); + RTMemFree(pMatch); + rcNt = STATUS_END_OF_FILE; + } + /* + * We found no error info. Return EOF. + */ + else + rcNt = STATUS_END_OF_FILE; + + RTSemMutexRelease(g_hErrorInfoLock); + } + else + rcNt = STATUS_UNSUCCESSFUL; + + /* Paranoia: Clear the buffer on failure. */ + if (!NT_SUCCESS(rcNt)) + { + PVOID pvDstBuf = pIrp->AssociatedIrp.SystemBuffer; + if ( pvDstBuf + && pStack->Parameters.Read.Length) + RT_BZERO(pvDstBuf, pStack->Parameters.Read.Length); + } + } + else + rcNt = STATUS_INVALID_PARAMETER; + } + else +#endif /* VBOX_WITH_HARDENING */ + { + /* + * Stub. + */ + rcNt = STATUS_NOT_SUPPORTED; + } + + /* + * Complete the request. + */ + pIrp->IoStatus.Status = rcNt; + IoCompleteRequest(pIrp, IO_NO_INCREMENT); + return rcNt; +} + + +/** + * Stub function for functions we don't implemented. + * + * @returns STATUS_NOT_SUPPORTED + * @param pDevObj Device object. + * @param pIrp IRP. + */ +NTSTATUS _stdcall VBoxDrvNtNotSupportedStub(PDEVICE_OBJECT pDevObj, PIRP pIrp) +{ + Log(("VBoxDrvNtNotSupportedStub\n")); + NOREF(pDevObj); + + pIrp->IoStatus.Information = 0; + pIrp->IoStatus.Status = STATUS_NOT_SUPPORTED; + IoCompleteRequest(pIrp, IO_NO_INCREMENT); + + return STATUS_NOT_SUPPORTED; +} + + +/** + * ExRegisterCallback handler for power events + * + * @param pCallbackContext User supplied parameter (pDevObj) + * @param pvArgument1 First argument + * @param pvArgument2 Second argument + */ +VOID _stdcall VBoxPowerDispatchCallback(PVOID pCallbackContext, PVOID pvArgument1, PVOID pvArgument2) +{ + /*PDEVICE_OBJECT pDevObj = (PDEVICE_OBJECT)pCallbackContext;*/ RT_NOREF1(pCallbackContext); + Log(("VBoxPowerDispatchCallback: %x %x\n", pvArgument1, pvArgument2)); + + /* Power change imminent? */ + if ((uintptr_t)pvArgument1 == PO_CB_SYSTEM_STATE_LOCK) + { + if (pvArgument2 == NULL) + Log(("VBoxPowerDispatchCallback: about to go into suspend mode!\n")); + else + Log(("VBoxPowerDispatchCallback: resumed!\n")); + + /* Inform any clients that have registered themselves with IPRT. */ + RTPowerSignalEvent(pvArgument2 == NULL ? RTPOWEREVENT_SUSPEND : RTPOWEREVENT_RESUME); + } +} + + +/** + * Called to clean up the session structure before it's freed. + * + * @param pDevExt The device globals. + * @param pSession The session that's being cleaned up. + */ +void VBOXCALL supdrvOSCleanupSession(PSUPDRVDEVEXT pDevExt, PSUPDRVSESSION pSession) +{ +#ifdef VBOX_WITH_HARDENING + if (pSession->pNtProtect) + { + supdrvNtProtectRelease(pSession->pNtProtect); + pSession->pNtProtect = NULL; + } + RT_NOREF1(pDevExt); +#else + RT_NOREF2(pDevExt, pSession); +#endif +#ifdef VBOXDRV_WITH_SID_TO_UID_MAPPING + if (pSession->pNtUserId) + { + supdrvNtUserIdRelease(pSession->pNtUserId); + pSession->pNtUserId = NULL; + } +#endif +} + + +void VBOXCALL supdrvOSSessionHashTabInserted(PSUPDRVDEVEXT pDevExt, PSUPDRVSESSION pSession, void *pvUser) +{ + NOREF(pDevExt); NOREF(pSession); NOREF(pvUser); +} + + +void VBOXCALL supdrvOSSessionHashTabRemoved(PSUPDRVDEVEXT pDevExt, PSUPDRVSESSION pSession, void *pvUser) +{ + NOREF(pDevExt); NOREF(pSession); NOREF(pvUser); +} + + +size_t VBOXCALL supdrvOSGipGetGroupTableSize(PSUPDRVDEVEXT pDevExt) +{ + NOREF(pDevExt); + uint32_t cMaxCpus = RTMpGetCount(); + uint32_t cGroups = RTMpGetMaxCpuGroupCount(); + + return cGroups * RT_UOFFSETOF(SUPGIPCPUGROUP, aiCpuSetIdxs) + + RT_SIZEOFMEMB(SUPGIPCPUGROUP, aiCpuSetIdxs[0]) * cMaxCpus; +} + + +int VBOXCALL supdrvOSInitGipGroupTable(PSUPDRVDEVEXT pDevExt, PSUPGLOBALINFOPAGE pGip, size_t cbGipCpuGroups) +{ + Assert(cbGipCpuGroups > 0); NOREF(cbGipCpuGroups); NOREF(pDevExt); + + unsigned const cGroups = RTMpGetMaxCpuGroupCount(); + AssertReturn(cGroups > 0 && cGroups < RT_ELEMENTS(pGip->aoffCpuGroup), VERR_INTERNAL_ERROR_2); + pGip->cPossibleCpuGroups = cGroups; + + PSUPGIPCPUGROUP pGroup = (PSUPGIPCPUGROUP)&pGip->aCPUs[pGip->cCpus]; + for (uint32_t idxGroup = 0; idxGroup < cGroups; idxGroup++) + { + uint32_t cActive = 0; + uint32_t const cMax = RTMpGetCpuGroupCounts(idxGroup, &cActive); + uint32_t const cbNeeded = RT_UOFFSETOF_DYN(SUPGIPCPUGROUP, aiCpuSetIdxs[cMax]); + uintptr_t const offGroup = (uintptr_t)pGroup - (uintptr_t)pGip; + AssertReturn(cbNeeded <= cbGipCpuGroups, VERR_INTERNAL_ERROR_3); + AssertReturn(cActive <= cMax, VERR_INTERNAL_ERROR_4); + AssertReturn(offGroup == (uint32_t)offGroup, VERR_INTERNAL_ERROR_5); + + pGip->aoffCpuGroup[idxGroup] = offGroup; + pGroup->cMembers = cActive; + pGroup->cMaxMembers = cMax; + for (uint32_t idxMember = 0; idxMember < cMax; idxMember++) + { + pGroup->aiCpuSetIdxs[idxMember] = RTMpSetIndexFromCpuGroupMember(idxGroup, idxMember); + Assert((unsigned)pGroup->aiCpuSetIdxs[idxMember] < pGip->cPossibleCpus); + } + + /* advance. */ + cbGipCpuGroups -= cbNeeded; + pGroup = (PSUPGIPCPUGROUP)&pGroup->aiCpuSetIdxs[cMax]; + } + + return VINF_SUCCESS; +} + + +void VBOXCALL supdrvOSGipInitGroupBitsForCpu(PSUPDRVDEVEXT pDevExt, PSUPGLOBALINFOPAGE pGip, PSUPGIPCPU pGipCpu) +{ + NOREF(pDevExt); + + /* + * Translate the CPU index into a group and member. + */ + PROCESSOR_NUMBER ProcNum = { 0, (UCHAR)pGipCpu->iCpuSet, 0 }; + if (g_pfnKeGetProcessorNumberFromIndex) + { + NTSTATUS rcNt = g_pfnKeGetProcessorNumberFromIndex(pGipCpu->iCpuSet, &ProcNum); + if (NT_SUCCESS(rcNt)) + Assert(ProcNum.Group < g_pfnKeQueryMaximumGroupCount()); + else + { + AssertFailed(); + ProcNum.Group = 0; + ProcNum.Number = pGipCpu->iCpuSet; + } + } + pGipCpu->iCpuGroup = ProcNum.Group; + pGipCpu->iCpuGroupMember = ProcNum.Number; + + /* + * Update the group info. Just do this wholesale for now (doesn't scale well). + */ + for (uint32_t idxGroup = 0; idxGroup < pGip->cPossibleCpuGroups; idxGroup++) + { + uint32_t offGroup = pGip->aoffCpuGroup[idxGroup]; + if (offGroup != UINT32_MAX) + { + PSUPGIPCPUGROUP pGroup = (PSUPGIPCPUGROUP)((uintptr_t)pGip + offGroup); + uint32_t cActive = 0; + uint32_t cMax = RTMpGetCpuGroupCounts(idxGroup, &cActive); + + AssertStmt(cMax == pGroup->cMaxMembers, cMax = pGroup->cMaxMembers); + AssertStmt(cActive <= cMax, cActive = cMax); + if (pGroup->cMembers != cActive) + ASMAtomicWriteU16(&pGroup->cMembers, cActive); + + for (uint32_t idxMember = 0; idxMember < cMax; idxMember++) + { + int idxCpuSet = RTMpSetIndexFromCpuGroupMember(idxGroup, idxMember); + AssertMsg((unsigned)idxCpuSet < pGip->cPossibleCpus, + ("%d vs %d for %u.%u\n", idxCpuSet, pGip->cPossibleCpus, idxGroup, idxMember)); + + if (pGroup->aiCpuSetIdxs[idxMember] != idxCpuSet) + ASMAtomicWriteS16(&pGroup->aiCpuSetIdxs[idxMember], idxCpuSet); + } + } + } +} + + +/** + * Initializes any OS specific object creator fields. + */ +void VBOXCALL supdrvOSObjInitCreator(PSUPDRVOBJ pObj, PSUPDRVSESSION pSession) +{ + NOREF(pObj); + NOREF(pSession); +} + + +/** + * Checks if the session can access the object. + * + * @returns true if a decision has been made. + * @returns false if the default access policy should be applied. + * + * @param pObj The object in question. + * @param pSession The session wanting to access the object. + * @param pszObjName The object name, can be NULL. + * @param prc Where to store the result when returning true. + */ +bool VBOXCALL supdrvOSObjCanAccess(PSUPDRVOBJ pObj, PSUPDRVSESSION pSession, const char *pszObjName, int *prc) +{ + NOREF(pObj); + NOREF(pSession); + NOREF(pszObjName); + NOREF(prc); + return false; +} + + +/** + * Force async tsc mode (stub). + */ +bool VBOXCALL supdrvOSGetForcedAsyncTscMode(PSUPDRVDEVEXT pDevExt) +{ + RT_NOREF1(pDevExt); + return g_Options.fOptForceAsyncTsc != 0; +} + + +/** + * Whether the host takes CPUs offline during a suspend/resume operation. + */ +bool VBOXCALL supdrvOSAreCpusOfflinedOnSuspend(void) +{ + return false; +} + + +/** + * Whether the hardware TSC has been synchronized by the OS. + */ +bool VBOXCALL supdrvOSAreTscDeltasInSync(void) +{ + /* If IPRT didn't find KeIpiGenericCall we pretend windows(, the firmware, + or whoever) always configures TSCs perfectly. */ + return !RTMpOnPairIsConcurrentExecSupported(); +} + + +#define MY_SystemLoadGdiDriverInSystemSpaceInformation 54 +#define MY_SystemUnloadGdiDriverInformation 27 + +typedef struct MYSYSTEMGDIDRIVERINFO +{ + UNICODE_STRING Name; /**< In: image file name. */ + PVOID ImageAddress; /**< Out: the load address. */ + PVOID SectionPointer; /**< Out: section object. */ + PVOID EntryPointer; /**< Out: entry point address. */ + PVOID ExportSectionPointer; /**< Out: export directory/section. */ + ULONG ImageLength; /**< Out: SizeOfImage. */ +} MYSYSTEMGDIDRIVERINFO; + +extern "C" __declspec(dllimport) NTSTATUS NTAPI ZwSetSystemInformation(ULONG, PVOID, ULONG); + +int VBOXCALL supdrvOSLdrOpen(PSUPDRVDEVEXT pDevExt, PSUPDRVLDRIMAGE pImage, const char *pszFilename) +{ + pImage->pvNtSectionObj = NULL; + pImage->hMemLock = NIL_RTR0MEMOBJ; + +#ifdef VBOX_WITHOUT_NATIVE_R0_LOADER +# ifndef RT_ARCH_X86 +# error "VBOX_WITHOUT_NATIVE_R0_LOADER is only safe on x86." +# endif + NOREF(pDevExt); NOREF(pszFilename); NOREF(pImage); + return VERR_NOT_SUPPORTED; + +#else + /* + * Convert the filename from DOS UTF-8 to NT UTF-16. + */ + size_t cwcFilename; + int rc = RTStrCalcUtf16LenEx(pszFilename, RTSTR_MAX, &cwcFilename); + if (RT_FAILURE(rc)) + return rc; + + PRTUTF16 pwcsFilename = (PRTUTF16)RTMemTmpAlloc((4 + cwcFilename + 1) * sizeof(RTUTF16)); + if (!pwcsFilename) + return VERR_NO_TMP_MEMORY; + + pwcsFilename[0] = '\\'; + pwcsFilename[1] = '?'; + pwcsFilename[2] = '?'; + pwcsFilename[3] = '\\'; + PRTUTF16 pwcsTmp = &pwcsFilename[4]; + rc = RTStrToUtf16Ex(pszFilename, RTSTR_MAX, &pwcsTmp, cwcFilename + 1, NULL); + if (RT_SUCCESS(rc)) + { + /* + * Try load it. + */ + MYSYSTEMGDIDRIVERINFO Info; + RtlInitUnicodeString(&Info.Name, pwcsFilename); + Info.ImageAddress = NULL; + Info.SectionPointer = NULL; + Info.EntryPointer = NULL; + Info.ExportSectionPointer = NULL; + Info.ImageLength = 0; + + NTSTATUS rcNt = ZwSetSystemInformation(MY_SystemLoadGdiDriverInSystemSpaceInformation, &Info, sizeof(Info)); + if (NT_SUCCESS(rcNt)) + { + pImage->pvImage = Info.ImageAddress; + pImage->pvNtSectionObj = Info.SectionPointer; + Log(("ImageAddress=%p SectionPointer=%p ImageLength=%#x cbImageBits=%#x rcNt=%#x '%ls'\n", + Info.ImageAddress, Info.SectionPointer, Info.ImageLength, pImage->cbImageBits, rcNt, Info.Name.Buffer)); +# ifdef DEBUG_bird + SUPR0Printf("ImageAddress=%p SectionPointer=%p ImageLength=%#x cbImageBits=%#x rcNt=%#x '%ls'\n", + Info.ImageAddress, Info.SectionPointer, Info.ImageLength, pImage->cbImageBits, rcNt, Info.Name.Buffer); +# endif + if (pImage->cbImageBits == Info.ImageLength) + { + /* + * Lock down the entire image, just to be on the safe side. + */ + rc = RTR0MemObjLockKernel(&pImage->hMemLock, pImage->pvImage, pImage->cbImageBits, RTMEM_PROT_READ); + if (RT_FAILURE(rc)) + { + pImage->hMemLock = NIL_RTR0MEMOBJ; + supdrvOSLdrUnload(pDevExt, pImage); + } + } + else + { + supdrvOSLdrUnload(pDevExt, pImage); + rc = VERR_LDR_MISMATCH_NATIVE; + } + } + else + { + Log(("rcNt=%#x '%ls'\n", rcNt, pwcsFilename)); + SUPR0Printf("VBoxDrv: rcNt=%x '%ws'\n", rcNt, pwcsFilename); + switch (rcNt) + { + case /* 0xc0000003 */ STATUS_INVALID_INFO_CLASS: +# ifdef RT_ARCH_AMD64 + /* Unwind will crash and BSOD, so no fallback here! */ + rc = VERR_NOT_IMPLEMENTED; +# else + /* + * Use the old way of loading the modules. + * + * Note! We do *NOT* try class 26 because it will probably + * not work correctly on terminal servers and such. + */ + rc = VERR_NOT_SUPPORTED; +# endif + break; + case /* 0xc0000034 */ STATUS_OBJECT_NAME_NOT_FOUND: + rc = VERR_MODULE_NOT_FOUND; + break; + case /* 0xC0000263 */ STATUS_DRIVER_ENTRYPOINT_NOT_FOUND: + rc = VERR_LDR_IMPORTED_SYMBOL_NOT_FOUND; + break; + case /* 0xC0000428 */ STATUS_INVALID_IMAGE_HASH: + rc = VERR_LDR_IMAGE_HASH; + break; + case /* 0xC000010E */ STATUS_IMAGE_ALREADY_LOADED: + Log(("WARNING: see @bugref{4853} for cause of this failure on Windows 7 x64\n")); + rc = VERR_ALREADY_LOADED; + break; + default: + rc = VERR_LDR_GENERAL_FAILURE; + break; + } + + pImage->pvNtSectionObj = NULL; + } + } + + RTMemTmpFree(pwcsFilename); + NOREF(pDevExt); + return rc; +#endif +} + + +void VBOXCALL supdrvOSLdrNotifyOpened(PSUPDRVDEVEXT pDevExt, PSUPDRVLDRIMAGE pImage, const char *pszFilename) +{ + NOREF(pDevExt); NOREF(pImage); NOREF(pszFilename); +} + + +void VBOXCALL supdrvOSLdrNotifyUnloaded(PSUPDRVDEVEXT pDevExt, PSUPDRVLDRIMAGE pImage) +{ + NOREF(pDevExt); NOREF(pImage); +} + + +/** + * Common worker for supdrvOSLdrQuerySymbol and supdrvOSLdrValidatePointer. + * + * @note Similar code in rtR0DbgKrnlNtParseModule. + */ +static int supdrvOSLdrValidatePointerOrQuerySymbol(PSUPDRVLDRIMAGE pImage, void *pv, const char *pszSymbol, + size_t cchSymbol, void **ppvSymbol) +{ + AssertReturn(pImage->pvNtSectionObj, VERR_INVALID_STATE); + Assert(pszSymbol || !ppvSymbol); + + /* + * Locate the export directory in the loaded image. + */ + uint8_t const *pbMapping = (uint8_t const *)pImage->pvImage; + uint32_t const cbMapping = pImage->cbImageBits; + uint32_t const uRvaToValidate = (uint32_t)((uintptr_t)pv - (uintptr_t)pbMapping); + AssertReturn(uRvaToValidate < cbMapping || ppvSymbol, VERR_INTERNAL_ERROR_3); + + uint32_t const offNtHdrs = *(uint16_t *)pbMapping == IMAGE_DOS_SIGNATURE + ? ((IMAGE_DOS_HEADER const *)pbMapping)->e_lfanew + : 0; + AssertLogRelReturn(offNtHdrs + sizeof(IMAGE_NT_HEADERS) < cbMapping, VERR_INTERNAL_ERROR_5); + + IMAGE_NT_HEADERS const *pNtHdrs = (IMAGE_NT_HEADERS const *)((uintptr_t)pbMapping + offNtHdrs); + AssertLogRelReturn(pNtHdrs->Signature == IMAGE_NT_SIGNATURE, VERR_INVALID_EXE_SIGNATURE); + AssertLogRelReturn(pNtHdrs->OptionalHeader.Magic == IMAGE_NT_OPTIONAL_HDR_MAGIC, VERR_BAD_EXE_FORMAT); + AssertLogRelReturn(pNtHdrs->OptionalHeader.NumberOfRvaAndSizes == IMAGE_NUMBEROF_DIRECTORY_ENTRIES, VERR_BAD_EXE_FORMAT); + + uint32_t const offEndSectHdrs = offNtHdrs + + sizeof(*pNtHdrs) + + pNtHdrs->FileHeader.NumberOfSections * sizeof(IMAGE_SECTION_HEADER); + AssertReturn(offEndSectHdrs < cbMapping, VERR_BAD_EXE_FORMAT); + + /* + * Find the export directory. + */ + IMAGE_DATA_DIRECTORY ExpDir = pNtHdrs->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT]; + if (!ExpDir.Size) + { + SUPR0Printf("SUPDrv: No exports in %s!\n", pImage->szName); + return ppvSymbol ? VERR_SYMBOL_NOT_FOUND : VERR_NOT_FOUND; + } + AssertReturn( ExpDir.Size >= sizeof(IMAGE_EXPORT_DIRECTORY) + && ExpDir.VirtualAddress >= offEndSectHdrs + && ExpDir.VirtualAddress < cbMapping + && ExpDir.VirtualAddress + ExpDir.Size <= cbMapping, VERR_BAD_EXE_FORMAT); + + IMAGE_EXPORT_DIRECTORY const *pExpDir = (IMAGE_EXPORT_DIRECTORY const *)&pbMapping[ExpDir.VirtualAddress]; + + uint32_t const cNamedExports = pExpDir->NumberOfNames; + AssertReturn(cNamedExports < _1M, VERR_BAD_EXE_FORMAT); + AssertReturn(pExpDir->NumberOfFunctions < _1M, VERR_BAD_EXE_FORMAT); + if (pExpDir->NumberOfFunctions == 0 || cNamedExports == 0) + { + SUPR0Printf("SUPDrv: No exports in %s!\n", pImage->szName); + return ppvSymbol ? VERR_SYMBOL_NOT_FOUND : VERR_NOT_FOUND; + } + + uint32_t const cExports = RT_MAX(cNamedExports, pExpDir->NumberOfFunctions); + + AssertReturn( pExpDir->AddressOfFunctions >= offEndSectHdrs + && pExpDir->AddressOfFunctions < cbMapping + && pExpDir->AddressOfFunctions + cExports * sizeof(uint32_t) <= cbMapping, + VERR_BAD_EXE_FORMAT); + uint32_t const * const paoffExports = (uint32_t const *)&pbMapping[pExpDir->AddressOfFunctions]; + + AssertReturn( pExpDir->AddressOfNames >= offEndSectHdrs + && pExpDir->AddressOfNames < cbMapping + && pExpDir->AddressOfNames + cNamedExports * sizeof(uint32_t) <= cbMapping, + VERR_BAD_EXE_FORMAT); + uint32_t const * const paoffNamedExports = (uint32_t const *)&pbMapping[pExpDir->AddressOfNames]; + + AssertReturn( pExpDir->AddressOfNameOrdinals >= offEndSectHdrs + && pExpDir->AddressOfNameOrdinals < cbMapping + && pExpDir->AddressOfNameOrdinals + cNamedExports * sizeof(uint32_t) <= cbMapping, + VERR_BAD_EXE_FORMAT); + uint16_t const * const pau16NameOrdinals = (uint16_t const *)&pbMapping[pExpDir->AddressOfNameOrdinals]; + + /* + * Validate the entrypoint RVA by scanning the export table. + */ + uint32_t iExportOrdinal = UINT32_MAX; + if (!ppvSymbol) + { + for (uint32_t i = 0; i < cExports; i++) + if (paoffExports[i] == uRvaToValidate) + { + iExportOrdinal = i; + break; + } + if (iExportOrdinal == UINT32_MAX) + { + SUPR0Printf("SUPDrv: No export with rva %#x (%s) in %s!\n", uRvaToValidate, pszSymbol, pImage->szName); + return VERR_NOT_FOUND; + } + } + + /* + * Can we validate the symbol name too or should we find a name? + * If so, just do a linear search. + */ + if (pszSymbol && (RT_C_IS_UPPER(*pszSymbol) || ppvSymbol)) + { + for (uint32_t i = 0; i < cNamedExports; i++) + { + uint32_t const offName = paoffNamedExports[i]; + AssertReturn(offName < cbMapping, VERR_BAD_EXE_FORMAT); + uint32_t const cchMaxName = cbMapping - offName; + const char * const pszName = (const char *)&pbMapping[offName]; + const char * const pszEnd = (const char *)memchr(pszName, '\0', cchMaxName); + AssertReturn(pszEnd, VERR_BAD_EXE_FORMAT); + + if ( cchSymbol == (size_t)(pszEnd - pszName) + && memcmp(pszName, pszSymbol, cchSymbol) == 0) + { + if (ppvSymbol) + { + iExportOrdinal = pau16NameOrdinals[i]; + if ( iExportOrdinal < cExports + && paoffExports[iExportOrdinal] < cbMapping) + { + *ppvSymbol = (void *)(paoffExports[iExportOrdinal] + pbMapping); + return VINF_SUCCESS; + } + } + else if (pau16NameOrdinals[i] == iExportOrdinal) + return VINF_SUCCESS; + else + SUPR0Printf("SUPDrv: Different exports found for %s and rva %#x in %s: %#x vs %#x\n", + pszSymbol, uRvaToValidate, pImage->szName, pau16NameOrdinals[i], iExportOrdinal); + return VERR_LDR_BAD_FIXUP; + } + } + if (!ppvSymbol) + SUPR0Printf("SUPDrv: No export named %s (%#x) in %s!\n", pszSymbol, uRvaToValidate, pImage->szName); + return VERR_SYMBOL_NOT_FOUND; + } + return VINF_SUCCESS; +} + + +int VBOXCALL supdrvOSLdrValidatePointer(PSUPDRVDEVEXT pDevExt, PSUPDRVLDRIMAGE pImage, void *pv, + const uint8_t *pbImageBits, const char *pszSymbol) +{ + RT_NOREF(pDevExt, pbImageBits); + return supdrvOSLdrValidatePointerOrQuerySymbol(pImage, pv, pszSymbol, pszSymbol ? strlen(pszSymbol) : 0, NULL); +} + + +int VBOXCALL supdrvOSLdrQuerySymbol(PSUPDRVDEVEXT pDevExt, PSUPDRVLDRIMAGE pImage, + const char *pszSymbol, size_t cchSymbol, void **ppvSymbol) +{ + RT_NOREF(pDevExt); + AssertReturn(ppvSymbol, VERR_INVALID_PARAMETER); + AssertReturn(pszSymbol, VERR_INVALID_PARAMETER); + return supdrvOSLdrValidatePointerOrQuerySymbol(pImage, NULL, pszSymbol, cchSymbol, ppvSymbol); +} + + +/** + * memcmp + errormsg + log. + * + * @returns Same as memcmp. + * @param pImage The image. + * @param pbImageBits The image bits ring-3 uploads. + * @param uRva The RVA to start comparing at. + * @param cb The number of bytes to compare. + * @param pReq The load request. + */ +static int supdrvNtCompare(PSUPDRVLDRIMAGE pImage, const uint8_t *pbImageBits, uint32_t uRva, uint32_t cb, PSUPLDRLOAD pReq) +{ + int iDiff = memcmp((uint8_t const *)pImage->pvImage + uRva, pbImageBits + uRva, cb); + if (iDiff) + { + uint32_t cbLeft = cb; + const uint8_t *pbNativeBits = (const uint8_t *)pImage->pvImage; + for (size_t off = uRva; cbLeft > 0; off++, cbLeft--) + if (pbNativeBits[off] != pbImageBits[off]) + { + /* Note! We need to copy image bits into a temporary stack buffer here as we'd + otherwise risk overwriting them while formatting the error message. */ + uint8_t abBytes[64]; + memcpy(abBytes, &pbImageBits[off], RT_MIN(64, cbLeft)); + supdrvLdrLoadError(VERR_LDR_MISMATCH_NATIVE, pReq, + "Mismatch at %#x (%p) of %s loaded at %p:\n" + "ntld: %.*Rhxs\n" + "iprt: %.*Rhxs", + off, &pbNativeBits[off], pImage->szName, pImage->pvImage, + RT_MIN(64, cbLeft), &pbNativeBits[off], + RT_MIN(64, cbLeft), &abBytes[0]); + SUPR0Printf("VBoxDrv: %s", pReq->u.Out.szError); + break; + } + } + return iDiff; +} + +/** Image compare exclusion regions. */ +typedef struct SUPDRVNTEXCLREGIONS +{ + /** Number of regions. */ + uint32_t cRegions; + /** The regions. */ + struct SUPDRVNTEXCLREGION + { + uint32_t uRva; + uint32_t cb; + } aRegions[20]; +} SUPDRVNTEXCLREGIONS; + +/** + * Adds an exclusion region to the collection. + */ +static bool supdrvNtAddExclRegion(SUPDRVNTEXCLREGIONS *pRegions, uint32_t uRvaRegion, uint32_t cbRegion) +{ + uint32_t const cRegions = pRegions->cRegions; + AssertReturn(cRegions + 1 <= RT_ELEMENTS(pRegions->aRegions), false); + uint32_t i = 0; + for (; i < cRegions; i++) + if (uRvaRegion < pRegions->aRegions[i].uRva) + break; + if (i != cRegions) + memmove(&pRegions->aRegions[i + 1], &pRegions->aRegions[i], (cRegions - i) * sizeof(pRegions->aRegions[0])); + pRegions->aRegions[i].uRva = uRvaRegion; + pRegions->aRegions[i].cb = cbRegion; + pRegions->cRegions++; + return true; +} + + +int VBOXCALL supdrvOSLdrLoad(PSUPDRVDEVEXT pDevExt, PSUPDRVLDRIMAGE pImage, const uint8_t *pbImageBits, PSUPLDRLOAD pReq) +{ + NOREF(pDevExt); + if (pImage->pvNtSectionObj) + { + /* + * Usually, the entire image matches exactly. + */ + if (!memcmp(pImage->pvImage, pbImageBits, pImage->cbImageBits)) + return VINF_SUCCESS; + + /* + * On Windows 10 the ImageBase member of the optional header is sometimes + * updated with the actual load address and sometimes not. + * On older windows versions (builds <= 9200?), a user mode address is + * sometimes found in the image base field after upgrading to VC++ 14.2. + */ + uint32_t const offNtHdrs = *(uint16_t *)pbImageBits == IMAGE_DOS_SIGNATURE + ? ((IMAGE_DOS_HEADER const *)pbImageBits)->e_lfanew + : 0; + AssertLogRelReturn(offNtHdrs + sizeof(IMAGE_NT_HEADERS) < pImage->cbImageBits, VERR_INTERNAL_ERROR_5); + IMAGE_NT_HEADERS const *pNtHdrsIprt = (IMAGE_NT_HEADERS const *)(pbImageBits + offNtHdrs); + IMAGE_NT_HEADERS const *pNtHdrsNtLd = (IMAGE_NT_HEADERS const *)((uintptr_t)pImage->pvImage + offNtHdrs); + + uint32_t const offImageBase = offNtHdrs + RT_UOFFSETOF(IMAGE_NT_HEADERS, OptionalHeader.ImageBase); + uint32_t const cbImageBase = RT_SIZEOFMEMB(IMAGE_NT_HEADERS, OptionalHeader.ImageBase); + if ( pNtHdrsNtLd->OptionalHeader.ImageBase != pNtHdrsIprt->OptionalHeader.ImageBase + && pNtHdrsIprt->Signature == IMAGE_NT_SIGNATURE + && pNtHdrsIprt->OptionalHeader.Magic == IMAGE_NT_OPTIONAL_HDR_MAGIC + && !memcmp(pImage->pvImage, pbImageBits, offImageBase) + && !memcmp((uint8_t const *)pImage->pvImage + offImageBase + cbImageBase, + pbImageBits + offImageBase + cbImageBase, + pImage->cbImageBits - offImageBase - cbImageBase)) + return VINF_SUCCESS; + + /* + * On Windows Server 2003 (sp2 x86) both import thunk tables are fixed + * up and we typically get a mismatch in the INIT section. + * + * So, lets see if everything matches when excluding the + * OriginalFirstThunk tables and (maybe) the ImageBase member. + * For simplicity the max number of exclusion regions is set to 16. + */ + if ( pNtHdrsIprt->Signature == IMAGE_NT_SIGNATURE + && pNtHdrsIprt->OptionalHeader.Magic == IMAGE_NT_OPTIONAL_HDR_MAGIC + && pNtHdrsIprt->OptionalHeader.NumberOfRvaAndSizes > IMAGE_DIRECTORY_ENTRY_IMPORT + && pNtHdrsIprt->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].Size >= sizeof(IMAGE_IMPORT_DESCRIPTOR) + && pNtHdrsIprt->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress > sizeof(IMAGE_NT_HEADERS) + && pNtHdrsIprt->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress < pImage->cbImageBits + && pNtHdrsIprt->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_LOAD_CONFIG].Size >= sizeof(IMAGE_LOAD_CONFIG_DIRECTORY) + && pNtHdrsIprt->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_LOAD_CONFIG].VirtualAddress > sizeof(IMAGE_NT_HEADERS) + && pNtHdrsIprt->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_LOAD_CONFIG].VirtualAddress < pImage->cbImageBits) + { + SUPDRVNTEXCLREGIONS ExcludeRegions; + ExcludeRegions.cRegions = 0; + + /* ImageBase: */ + if (pNtHdrsNtLd->OptionalHeader.ImageBase != pNtHdrsIprt->OptionalHeader.ImageBase) + supdrvNtAddExclRegion(&ExcludeRegions, offImageBase, cbImageBase); + + /* Imports: */ + uint32_t cImpsLeft = pNtHdrsIprt->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].Size + / sizeof(IMAGE_IMPORT_DESCRIPTOR); + uint32_t offImps = pNtHdrsIprt->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress; + AssertLogRelReturn(offImps + cImpsLeft * sizeof(IMAGE_IMPORT_DESCRIPTOR) <= pImage->cbImageBits, VERR_INTERNAL_ERROR_3); + IMAGE_IMPORT_DESCRIPTOR const *pImp = (IMAGE_IMPORT_DESCRIPTOR const *)(pbImageBits + offImps); + while ( cImpsLeft-- > 0 + && ExcludeRegions.cRegions < RT_ELEMENTS(ExcludeRegions.aRegions)) + { + uint32_t uRvaThunk = pImp->OriginalFirstThunk; + if ( uRvaThunk > sizeof(IMAGE_NT_HEADERS) + && uRvaThunk <= pImage->cbImageBits - sizeof(IMAGE_THUNK_DATA) + && uRvaThunk != pImp->FirstThunk) + { + /* Find the size of the thunk table. */ + IMAGE_THUNK_DATA const *paThunk = (IMAGE_THUNK_DATA const *)(pbImageBits + uRvaThunk); + uint32_t cMaxThunks = (pImage->cbImageBits - uRvaThunk) / sizeof(IMAGE_THUNK_DATA); + uint32_t cThunks = 0; + while (cThunks < cMaxThunks && paThunk[cThunks].u1.Function != 0) + cThunks++; + supdrvNtAddExclRegion(&ExcludeRegions, uRvaThunk, cThunks * sizeof(IMAGE_THUNK_DATA)); + } + +#if 0 /* Useful for VMMR0 hacking, not for production use. See also SUPDrvLdr.cpp. */ + /* Exclude the other thunk table if ntoskrnl.exe. */ + uint32_t uRvaName = pImp->Name; + if ( uRvaName > sizeof(IMAGE_NT_HEADERS) + && uRvaName < pImage->cbImageBits - sizeof("ntoskrnl.exe") + && memcmp(&pbImageBits[uRvaName], RT_STR_TUPLE("ntoskrnl.exe")) == 0) + { + uRvaThunk = pImp->FirstThunk; + if ( uRvaThunk > sizeof(IMAGE_NT_HEADERS) + && uRvaThunk <= pImage->cbImageBits - sizeof(IMAGE_THUNK_DATA)) + { + /* Find the size of the thunk table. */ + IMAGE_THUNK_DATA const *paThunk = (IMAGE_THUNK_DATA const *)(pbImageBits + uRvaThunk); + uint32_t cMaxThunks = (pImage->cbImageBits - uRvaThunk) / sizeof(IMAGE_THUNK_DATA); + uint32_t cThunks = 0; + while (cThunks < cMaxThunks && paThunk[cThunks].u1.Function != 0) + cThunks++; + supdrvNtAddExclRegion(&ExcludeRegions, uRvaThunk, cThunks * sizeof(IMAGE_THUNK_DATA)); + } + } +#endif + + /* advance */ + pImp++; + } + + /* Exclude the security cookie if present. */ + uint32_t const cbCfg = pNtHdrsIprt->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_LOAD_CONFIG].Size; + uint32_t const offCfg = pNtHdrsIprt->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_LOAD_CONFIG].VirtualAddress; + IMAGE_LOAD_CONFIG_DIRECTORY const * const pCfg = (IMAGE_LOAD_CONFIG_DIRECTORY const *)&pbImageBits[offCfg]; + if ( pCfg->Size >= RT_UOFFSET_AFTER(IMAGE_LOAD_CONFIG_DIRECTORY, SecurityCookie) + && pCfg->SecurityCookie != NULL) + supdrvNtAddExclRegion(&ExcludeRegions, (uintptr_t)pCfg->SecurityCookie - (uintptr_t)pImage->pvImage, sizeof(void *)); + + /* Also exclude the GuardCFCheckFunctionPointer and GuardCFDispatchFunctionPointer pointer variables. */ + if ( pCfg->Size >= RT_UOFFSET_AFTER(IMAGE_LOAD_CONFIG_DIRECTORY, GuardCFCheckFunctionPointer) + && pCfg->GuardCFCheckFunctionPointer != NULL) + supdrvNtAddExclRegion(&ExcludeRegions, (uintptr_t)pCfg->GuardCFCheckFunctionPointer - (uintptr_t)pImage->pvImage, sizeof(void *)); + if ( pCfg->Size >= RT_UOFFSET_AFTER(IMAGE_LOAD_CONFIG_DIRECTORY, GuardCFDispatchFunctionPointer) + && pCfg->GuardCFDispatchFunctionPointer != NULL) + supdrvNtAddExclRegion(&ExcludeRegions, (uintptr_t)pCfg->GuardCFDispatchFunctionPointer - (uintptr_t)pImage->pvImage, sizeof(void *)); + + /* Ditto for the XFG variants: */ + if ( pCfg->Size >= RT_UOFFSET_AFTER(IMAGE_LOAD_CONFIG_DIRECTORY, GuardXFGCheckFunctionPointer) + && pCfg->GuardXFGCheckFunctionPointer != NULL) + supdrvNtAddExclRegion(&ExcludeRegions, (uintptr_t)pCfg->GuardXFGCheckFunctionPointer - (uintptr_t)pImage->pvImage, sizeof(void *)); + if ( pCfg->Size >= RT_UOFFSET_AFTER(IMAGE_LOAD_CONFIG_DIRECTORY, GuardXFGDispatchFunctionPointer) + && pCfg->GuardXFGDispatchFunctionPointer != NULL) + supdrvNtAddExclRegion(&ExcludeRegions, (uintptr_t)pCfg->GuardXFGDispatchFunctionPointer - (uintptr_t)pImage->pvImage, sizeof(void *)); + + /** @todo What about GuardRFVerifyStackPointerFunctionPointer and + * GuardRFFailureRoutineFunctionPointer? Ignore for now as the compiler we're + * using (19.26.28805) sets them to zero from what I can tell. */ + + /* + * Ok, do the comparison. + */ + int iDiff = 0; + uint32_t uRvaNext = 0; + for (unsigned i = 0; !iDiff && i < ExcludeRegions.cRegions; i++) + { + if (uRvaNext < ExcludeRegions.aRegions[i].uRva) + iDiff = supdrvNtCompare(pImage, pbImageBits, uRvaNext, ExcludeRegions.aRegions[i].uRva - uRvaNext, pReq); + uRvaNext = ExcludeRegions.aRegions[i].uRva + ExcludeRegions.aRegions[i].cb; + } + if (!iDiff && uRvaNext < pImage->cbImageBits) + iDiff = supdrvNtCompare(pImage, pbImageBits, uRvaNext, pImage->cbImageBits - uRvaNext, pReq); + if (!iDiff) + { + /* + * If there is a cookie init export, call it. + * + * This typically just does: + * __security_cookie = (rdtsc ^ &__security_cookie) & 0xffffffffffff; + * __security_cookie_complement = ~__security_cookie; + */ + PFNRT pfnModuleInitSecurityCookie = NULL; + int rcSym = supdrvOSLdrQuerySymbol(pDevExt, pImage, RT_STR_TUPLE("ModuleInitSecurityCookie"), + (void **)&pfnModuleInitSecurityCookie); + if (RT_SUCCESS(rcSym) && pfnModuleInitSecurityCookie) + pfnModuleInitSecurityCookie(); + + return VINF_SUCCESS; + } + } + else + supdrvNtCompare(pImage, pbImageBits, 0, pImage->cbImageBits, pReq); + return VERR_LDR_MISMATCH_NATIVE; + } + return supdrvLdrLoadError(VERR_INTERNAL_ERROR_4, pReq, "No NT section object! Impossible!"); +} + + +void VBOXCALL supdrvOSLdrUnload(PSUPDRVDEVEXT pDevExt, PSUPDRVLDRIMAGE pImage) +{ + if (pImage->pvNtSectionObj) + { + if (pImage->hMemLock != NIL_RTR0MEMOBJ) + { + RTR0MemObjFree(pImage->hMemLock, false /*fFreeMappings*/); + pImage->hMemLock = NIL_RTR0MEMOBJ; + } + + NTSTATUS rcNt = ZwSetSystemInformation(MY_SystemUnloadGdiDriverInformation, + &pImage->pvNtSectionObj, sizeof(pImage->pvNtSectionObj)); + if (rcNt != STATUS_SUCCESS) + SUPR0Printf("VBoxDrv: failed to unload '%s', rcNt=%#x\n", pImage->szName, rcNt); + pImage->pvNtSectionObj = NULL; + } + NOREF(pDevExt); +} + + +void VBOXCALL supdrvOSLdrRetainWrapperModule(PSUPDRVDEVEXT pDevExt, PSUPDRVLDRIMAGE pImage) +{ + RT_NOREF(pDevExt, pImage); + AssertFailed(); +} + + +void VBOXCALL supdrvOSLdrReleaseWrapperModule(PSUPDRVDEVEXT pDevExt, PSUPDRVLDRIMAGE pImage) +{ + RT_NOREF(pDevExt, pImage); + AssertFailed(); +} + + +#ifdef SUPDRV_WITH_MSR_PROBER + +#if 1 +/** @todo make this selectable. */ +# define AMD_MSR_PASSCODE 0x9c5a203a +#else +# define ASMRdMsrEx(a, b, c) ASMRdMsr(a) +# define ASMWrMsrEx(a, b, c) ASMWrMsr(a,c) +#endif + + +/** + * Argument package used by supdrvOSMsrProberRead and supdrvOSMsrProberWrite. + */ +typedef struct SUPDRVNTMSPROBERARGS +{ + uint32_t uMsr; + uint64_t uValue; + bool fGp; +} SUPDRVNTMSPROBERARGS; + +/** @callback_method_impl{FNRTMPWORKER, Worker for supdrvOSMsrProberRead.} */ +static DECLCALLBACK(void) supdrvNtMsProberReadOnCpu(RTCPUID idCpu, void *pvUser1, void *pvUser2) +{ + /* + * rdmsr and wrmsr faults can be caught even with interrupts disabled. + * (At least on 32-bit XP.) + */ + SUPDRVNTMSPROBERARGS *pArgs = (SUPDRVNTMSPROBERARGS *)pvUser1; NOREF(idCpu); NOREF(pvUser2); + RTCCUINTREG fOldFlags = ASMIntDisableFlags(); + __try + { + pArgs->uValue = ASMRdMsrEx(pArgs->uMsr, AMD_MSR_PASSCODE); + pArgs->fGp = false; + } + __except(EXCEPTION_EXECUTE_HANDLER) + { + pArgs->fGp = true; + pArgs->uValue = 0; + } + ASMSetFlags(fOldFlags); +} + + +int VBOXCALL supdrvOSMsrProberRead(uint32_t uMsr, RTCPUID idCpu, uint64_t *puValue) +{ + SUPDRVNTMSPROBERARGS Args; + Args.uMsr = uMsr; + Args.uValue = 0; + Args.fGp = true; + + if (idCpu == NIL_RTCPUID) + supdrvNtMsProberReadOnCpu(idCpu, &Args, NULL); + else + { + int rc = RTMpOnSpecific(idCpu, supdrvNtMsProberReadOnCpu, &Args, NULL); + if (RT_FAILURE(rc)) + return rc; + } + + if (Args.fGp) + return VERR_ACCESS_DENIED; + *puValue = Args.uValue; + return VINF_SUCCESS; +} + + +/** @callback_method_impl{FNRTMPWORKER, Worker for supdrvOSMsrProberWrite.} */ +static DECLCALLBACK(void) supdrvNtMsProberWriteOnCpu(RTCPUID idCpu, void *pvUser1, void *pvUser2) +{ + /* + * rdmsr and wrmsr faults can be caught even with interrupts disabled. + * (At least on 32-bit XP.) + */ + SUPDRVNTMSPROBERARGS *pArgs = (SUPDRVNTMSPROBERARGS *)pvUser1; NOREF(idCpu); NOREF(pvUser2); + RTCCUINTREG fOldFlags = ASMIntDisableFlags(); + __try + { + ASMWrMsrEx(pArgs->uMsr, AMD_MSR_PASSCODE, pArgs->uValue); + pArgs->fGp = false; + } + __except(EXCEPTION_EXECUTE_HANDLER) + { + pArgs->fGp = true; + } + ASMSetFlags(fOldFlags); +} + +int VBOXCALL supdrvOSMsrProberWrite(uint32_t uMsr, RTCPUID idCpu, uint64_t uValue) +{ + SUPDRVNTMSPROBERARGS Args; + Args.uMsr = uMsr; + Args.uValue = uValue; + Args.fGp = true; + + if (idCpu == NIL_RTCPUID) + supdrvNtMsProberWriteOnCpu(idCpu, &Args, NULL); + else + { + int rc = RTMpOnSpecific(idCpu, supdrvNtMsProberWriteOnCpu, &Args, NULL); + if (RT_FAILURE(rc)) + return rc; + } + + if (Args.fGp) + return VERR_ACCESS_DENIED; + return VINF_SUCCESS; +} + +/** @callback_method_impl{FNRTMPWORKER, Worker for supdrvOSMsrProberModify.} */ +static DECLCALLBACK(void) supdrvNtMsProberModifyOnCpu(RTCPUID idCpu, void *pvUser1, void *pvUser2) +{ + PSUPMSRPROBER pReq = (PSUPMSRPROBER)pvUser1; + register uint32_t uMsr = pReq->u.In.uMsr; + bool const fFaster = pReq->u.In.enmOp == SUPMSRPROBEROP_MODIFY_FASTER; + uint64_t uBefore = 0; + uint64_t uWritten = 0; + uint64_t uAfter = 0; + bool fBeforeGp = true; + bool fModifyGp = true; + bool fAfterGp = true; + bool fRestoreGp = true; + RTCCUINTREG fOldFlags; + RT_NOREF2(idCpu, pvUser2); + + /* + * Do the job. + */ + fOldFlags = ASMIntDisableFlags(); + ASMCompilerBarrier(); /* paranoia */ + if (!fFaster) + ASMWriteBackAndInvalidateCaches(); + + __try + { + uBefore = ASMRdMsrEx(uMsr, AMD_MSR_PASSCODE); + fBeforeGp = false; + } + __except(EXCEPTION_EXECUTE_HANDLER) + { + fBeforeGp = true; + } + if (!fBeforeGp) + { + register uint64_t uRestore = uBefore; + + /* Modify. */ + uWritten = uRestore; + uWritten &= pReq->u.In.uArgs.Modify.fAndMask; + uWritten |= pReq->u.In.uArgs.Modify.fOrMask; + __try + { + ASMWrMsrEx(uMsr, AMD_MSR_PASSCODE, uWritten); + fModifyGp = false; + } + __except(EXCEPTION_EXECUTE_HANDLER) + { + fModifyGp = true; + } + + /* Read modified value. */ + __try + { + uAfter = ASMRdMsrEx(uMsr, AMD_MSR_PASSCODE); + fAfterGp = false; + } + __except(EXCEPTION_EXECUTE_HANDLER) + { + fAfterGp = true; + } + + /* Restore original value. */ + __try + { + ASMWrMsrEx(uMsr, AMD_MSR_PASSCODE, uRestore); + fRestoreGp = false; + } + __except(EXCEPTION_EXECUTE_HANDLER) + { + fRestoreGp = true; + } + + /* Invalid everything we can. */ + if (!fFaster) + { + ASMWriteBackAndInvalidateCaches(); + ASMReloadCR3(); + ASMNopPause(); + } + } + + ASMCompilerBarrier(); /* paranoia */ + ASMSetFlags(fOldFlags); + + /* + * Write out the results. + */ + pReq->u.Out.uResults.Modify.uBefore = uBefore; + pReq->u.Out.uResults.Modify.uWritten = uWritten; + pReq->u.Out.uResults.Modify.uAfter = uAfter; + pReq->u.Out.uResults.Modify.fBeforeGp = fBeforeGp; + pReq->u.Out.uResults.Modify.fModifyGp = fModifyGp; + pReq->u.Out.uResults.Modify.fAfterGp = fAfterGp; + pReq->u.Out.uResults.Modify.fRestoreGp = fRestoreGp; + RT_ZERO(pReq->u.Out.uResults.Modify.afReserved); +} + + +int VBOXCALL supdrvOSMsrProberModify(RTCPUID idCpu, PSUPMSRPROBER pReq) +{ + if (idCpu == NIL_RTCPUID) + { + supdrvNtMsProberModifyOnCpu(idCpu, pReq, NULL); + return VINF_SUCCESS; + } + return RTMpOnSpecific(idCpu, supdrvNtMsProberModifyOnCpu, pReq, NULL); +} + +#endif /* SUPDRV_WITH_MSR_PROBER */ + + +/** + * Converts an IPRT error code to an nt status code. + * + * @returns corresponding nt status code. + * @param rc IPRT error status code. + */ +static NTSTATUS VBoxDrvNtErr2NtStatus(int rc) +{ + switch (rc) + { + case VINF_SUCCESS: return STATUS_SUCCESS; + case VERR_GENERAL_FAILURE: return STATUS_NOT_SUPPORTED; + case VERR_INVALID_PARAMETER: return STATUS_INVALID_PARAMETER; + case VERR_INVALID_MAGIC: return STATUS_UNKNOWN_REVISION; + case VERR_INVALID_HANDLE: return STATUS_INVALID_HANDLE; + case VERR_INVALID_POINTER: return STATUS_INVALID_ADDRESS; + case VERR_LOCK_FAILED: return STATUS_NOT_LOCKED; + case VERR_ALREADY_LOADED: return STATUS_IMAGE_ALREADY_LOADED; + case VERR_PERMISSION_DENIED: return STATUS_ACCESS_DENIED; + case VERR_VERSION_MISMATCH: return STATUS_REVISION_MISMATCH; + } + + if (rc < 0) + { + if (((uint32_t)rc & UINT32_C(0xffff0000)) == UINT32_C(0xffff0000)) + return (NTSTATUS)( ((uint32_t)rc & UINT32_C(0xffff)) | SUP_NT_STATUS_BASE ); + } + return STATUS_UNSUCCESSFUL; +} + + +SUPR0DECL(int) SUPR0PrintfV(const char *pszFormat, va_list va) +{ + char szMsg[384]; + size_t cch = RTStrPrintfV(szMsg, sizeof(szMsg) - 1, pszFormat, va); + szMsg[sizeof(szMsg) - 1] = '\0'; + + RTLogWriteDebugger(szMsg, cch); + return 0; +} + + +SUPR0DECL(uint32_t) SUPR0GetKernelFeatures(void) +{ + return 0; +} + + +SUPR0DECL(bool) SUPR0FpuBegin(bool fCtxHook) +{ + RT_NOREF(fCtxHook); + return false; +} + + +SUPR0DECL(void) SUPR0FpuEnd(bool fCtxHook) +{ + RT_NOREF(fCtxHook); +} + + +SUPR0DECL(int) SUPR0IoCtlSetupForHandle(PSUPDRVSESSION pSession, intptr_t hHandle, uint32_t fFlags, PSUPR0IOCTLCTX *ppCtx) +{ + /* + * Validate input. + */ + AssertPtrReturn(ppCtx, VERR_INVALID_POINTER); + *ppCtx = NULL; + AssertReturn(SUP_IS_SESSION_VALID(pSession), VERR_INVALID_PARAMETER); + AssertReturn(!fFlags, VERR_INVALID_FLAGS); + + /* + * Turn the partition handle into a file object and related device object + * so that we can issue direct I/O control calls to the pair later. + */ + PFILE_OBJECT pFileObject = NULL; + OBJECT_HANDLE_INFORMATION HandleInfo = { 0, 0 }; + NTSTATUS rcNt = ObReferenceObjectByHandle((HANDLE)hHandle, /*FILE_WRITE_DATA*/0, *IoFileObjectType, + UserMode, (void **)&pFileObject, &HandleInfo); + if (!NT_SUCCESS(rcNt)) + return RTErrConvertFromNtStatus(rcNt); + AssertPtrReturn(pFileObject, VERR_INTERNAL_ERROR_3); + + PDEVICE_OBJECT pDevObject = IoGetRelatedDeviceObject(pFileObject); + AssertMsgReturnStmt(RT_VALID_PTR(pDevObject), ("pDevObject=%p\n", pDevObject), + ObDereferenceObject(pFileObject), VERR_INTERNAL_ERROR_2); + + /* + * Allocate a context structure and fill it in. + */ + PSUPR0IOCTLCTX pCtx = (PSUPR0IOCTLCTX)RTMemAllocZ(sizeof(*pCtx)); + if (pCtx) + { + pCtx->u32Magic = SUPR0IOCTLCTX_MAGIC; + pCtx->cRefs = 1; + pCtx->pFileObject = pFileObject; + pCtx->pDeviceObject = pDevObject; + + PDRIVER_OBJECT pDrvObject = pDevObject->DriverObject; + if ( RT_VALID_PTR(pDrvObject->FastIoDispatch) + && RT_VALID_PTR(pDrvObject->FastIoDispatch->FastIoDeviceControl)) + pCtx->pfnFastIoDeviceControl = pDrvObject->FastIoDispatch->FastIoDeviceControl; + else + pCtx->pfnFastIoDeviceControl = NULL; + *ppCtx = pCtx; + return VINF_SUCCESS; + } + + ObDereferenceObject(pFileObject); + return VERR_NO_MEMORY; +} + + +/** + * I/O control destructor for NT. + * + * @param pCtx The context to destroy. + */ +static void supdrvNtIoCtlContextDestroy(PSUPR0IOCTLCTX pCtx) +{ + PFILE_OBJECT pFileObject = pCtx->pFileObject; + pCtx->pfnFastIoDeviceControl = NULL; + pCtx->pFileObject = NULL; + pCtx->pDeviceObject = NULL; + ASMAtomicWriteU32(&pCtx->u32Magic, ~SUPR0IOCTLCTX_MAGIC); + + if (RT_VALID_PTR(pFileObject)) + ObDereferenceObject(pFileObject); + RTMemFree(pCtx); +} + + +SUPR0DECL(int) SUPR0IoCtlCleanup(PSUPR0IOCTLCTX pCtx) +{ + if (pCtx != NULL) + { + AssertPtrReturn(pCtx, VERR_INVALID_POINTER); + AssertReturn(pCtx->u32Magic == SUPR0IOCTLCTX_MAGIC, VERR_INVALID_PARAMETER); + + uint32_t cRefs = ASMAtomicDecU32(&pCtx->cRefs); + Assert(cRefs < _4K); + if (cRefs == 0) + supdrvNtIoCtlContextDestroy(pCtx); + } + return VINF_SUCCESS; +} + + +SUPR0DECL(int) SUPR0IoCtlPerform(PSUPR0IOCTLCTX pCtx, uintptr_t uFunction, + void *pvInput, RTR3PTR pvInputUser, size_t cbInput, + void *pvOutput, RTR3PTR pvOutputUser, size_t cbOutput, + int32_t *piNativeRc) +{ + AssertPtrReturn(pCtx, VERR_INVALID_POINTER); + AssertReturn(pCtx->u32Magic == SUPR0IOCTLCTX_MAGIC, VERR_INVALID_PARAMETER); + + /* Reference the context. */ + uint32_t cRefs = ASMAtomicIncU32(&pCtx->cRefs); + Assert(cRefs > 1 && cRefs < _4K); + + /* + * Try fast I/O control path first. + */ + IO_STATUS_BLOCK Ios = RTNT_IO_STATUS_BLOCK_INITIALIZER; + if (pCtx->pfnFastIoDeviceControl) + { + /* Must pass user addresses here as that's what's being expected. */ + BOOLEAN fHandled = pCtx->pfnFastIoDeviceControl(pCtx->pFileObject, + TRUE /*Wait*/, + (void *)pvInputUser, (ULONG)cbInput, + (void *)pvOutputUser, (ULONG)cbOutput, + uFunction, + &Ios, + pCtx->pDeviceObject); + if (fHandled) + { + /* Relase the context. */ + cRefs = ASMAtomicDecU32(&pCtx->cRefs); + Assert(cRefs < _4K); + if (cRefs == 0) + supdrvNtIoCtlContextDestroy(pCtx); + + /* Set/convert status and return. */ + if (piNativeRc) + { + *piNativeRc = Ios.Status; + return VINF_SUCCESS; + } + if (NT_SUCCESS(Ios.Status)) + return VINF_SUCCESS; + return RTErrConvertFromNtStatus(Ios.Status); + } + + /* + * Fall back on IRP if not handled. + * + * Note! Perhaps we should rather fail, because VID.SYS will crash getting + * the partition ID with the code below. It tries to zero the output + * buffer as if it were as system buffer... + */ + RTNT_IO_STATUS_BLOCK_REINIT(&Ios); + } + + /* + * For directly accessed buffers we must supply user mode addresses or + * we'll fail ProbeForWrite validation. + */ + switch (uFunction & 3) + { + case METHOD_BUFFERED: + /* For buffered accesses, we can supply kernel buffers. */ + break; + + case METHOD_IN_DIRECT: + pvInput = (void *)pvInputUser; + break; + + case METHOD_NEITHER: + pvInput = (void *)pvInputUser; + RT_FALL_THRU(); + + case METHOD_OUT_DIRECT: + pvOutput = (void *)pvOutputUser; + break; + } + + /* + * Build the request. + */ + int rc; + KEVENT Event; + KeInitializeEvent(&Event, NotificationEvent, FALSE); + + PIRP pIrp = IoBuildDeviceIoControlRequest(uFunction, pCtx->pDeviceObject, + pvInput, (ULONG)cbInput, pvOutput, (ULONG)cbOutput, + FALSE /* InternalDeviceControl */, &Event, &Ios); + if (pIrp) + { + IoGetNextIrpStackLocation(pIrp)->FileObject = pCtx->pFileObject; + + /* + * Make the call. + */ + NTSTATUS rcNt = IoCallDriver(pCtx->pDeviceObject, pIrp); + if (rcNt == STATUS_PENDING) + { + rcNt = KeWaitForSingleObject(&Event, /* Object */ + Executive, /* WaitReason */ + KernelMode, /* WaitMode */ + FALSE, /* Alertable */ + NULL); /* TimeOut */ + AssertMsg(rcNt == STATUS_SUCCESS, ("rcNt=%#x\n", rcNt)); + rcNt = Ios.Status; + } + else if (NT_SUCCESS(rcNt) && Ios.Status != STATUS_SUCCESS) + rcNt = Ios.Status; + + /* Set/convert return code. */ + if (piNativeRc) + { + *piNativeRc = rcNt; + rc = VINF_SUCCESS; + } + else if (NT_SUCCESS(rcNt)) + rc = VINF_SUCCESS; + else + rc = RTErrConvertFromNtStatus(rcNt); + } + else + { + if (piNativeRc) + *piNativeRc = STATUS_NO_MEMORY; + rc = VERR_NO_MEMORY; + } + + /* Relase the context. */ + cRefs = ASMAtomicDecU32(&pCtx->cRefs); + Assert(cRefs < _4K); + if (cRefs == 0) + supdrvNtIoCtlContextDestroy(pCtx); + + return rc; +} + + +#ifdef VBOX_WITH_HARDENING + +/** @name Identifying Special Processes: CSRSS.EXE + * @{ */ + + +/** + * Checks if the process is a system32 process by the given name. + * + * @returns true / false. + * @param pProcess The process to check. + * @param pszName The lower case process name (no path!). + */ +static bool supdrvNtProtectIsSystem32ProcessMatch(PEPROCESS pProcess, const char *pszName) +{ + Assert(strlen(pszName) < 16); /* see buffer below */ + + /* + * This test works on XP+. + */ + const char *pszImageFile = (const char *)PsGetProcessImageFileName(pProcess); + if (!pszImageFile) + return false; + + if (RTStrICmp(pszImageFile, pszName) != 0) + return false; + + /* + * This test requires a Vista+ API. + */ + if (g_pfnPsReferenceProcessFilePointer) + { + PFILE_OBJECT pFile = NULL; + NTSTATUS rcNt = g_pfnPsReferenceProcessFilePointer(pProcess, &pFile); + if (!NT_SUCCESS(rcNt)) + return false; + + union + { + OBJECT_NAME_INFORMATION Info; + uint8_t abBuffer[sizeof(g_System32NtPath) + 16 * sizeof(WCHAR)]; + } Buf; + ULONG cbIgn; + rcNt = ObQueryNameString(pFile, &Buf.Info, sizeof(Buf) - sizeof(WCHAR), &cbIgn); + ObDereferenceObject(pFile); + if (!NT_SUCCESS(rcNt)) + return false; + + /* Terminate the name. */ + PRTUTF16 pwszName = Buf.Info.Name.Buffer; + pwszName[Buf.Info.Name.Length / sizeof(RTUTF16)] = '\0'; + + /* Match the name against the system32 directory path. */ + uint32_t cbSystem32 = g_System32NtPath.UniStr.Length; + if (Buf.Info.Name.Length < cbSystem32) + return false; + if (memcmp(pwszName, g_System32NtPath.UniStr.Buffer, cbSystem32)) + return false; + pwszName += cbSystem32 / sizeof(RTUTF16); + if (*pwszName++ != '\\') + return false; + + /* Compare the name. */ + const char *pszRight = pszName; + for (;;) + { + WCHAR wchLeft = *pwszName++; + char chRight = *pszRight++; + Assert(chRight == RT_C_TO_LOWER(chRight)); + + if ( wchLeft != chRight + && RT_C_TO_LOWER(wchLeft) != chRight) + return false; + if (!chRight) + break; + } + } + + return true; +} + + +/** + * Checks if the current process is likely to be CSRSS. + * + * @returns true/false. + * @param pProcess The process. + */ +static bool supdrvNtProtectIsCsrssByProcess(PEPROCESS pProcess) +{ + /* + * On Windows 8.1 CSRSS.EXE is a protected process. + */ + if (g_pfnPsIsProtectedProcessLight) + { + if (!g_pfnPsIsProtectedProcessLight(pProcess)) + return false; + } + + /* + * The name tests. + */ + if (!supdrvNtProtectIsSystem32ProcessMatch(pProcess, "csrss.exe")) + return false; + + /** @todo Could extend the CSRSS.EXE check with that the TokenUser of the + * current process must be "NT AUTHORITY\SYSTEM" (S-1-5-18). */ + + return true; +} + + +/** + * Helper for supdrvNtProtectGetAlpcPortObjectType that tries out a name. + * + * @returns true if done, false if not. + * @param pwszPortNm The port path. + * @param ppObjType The object type return variable, updated when + * returning true. + */ +static bool supdrvNtProtectGetAlpcPortObjectType2(PCRTUTF16 pwszPortNm, POBJECT_TYPE *ppObjType) +{ + bool fDone = false; + + UNICODE_STRING UniStrPortNm; + UniStrPortNm.Buffer = (WCHAR *)pwszPortNm; + UniStrPortNm.Length = (USHORT)(RTUtf16Len(pwszPortNm) * sizeof(WCHAR)); + UniStrPortNm.MaximumLength = UniStrPortNm.Length + sizeof(WCHAR); + + OBJECT_ATTRIBUTES ObjAttr; + InitializeObjectAttributes(&ObjAttr, &UniStrPortNm, OBJ_CASE_INSENSITIVE | OBJ_KERNEL_HANDLE, NULL, NULL); + + HANDLE hPort; + NTSTATUS rcNt = g_pfnZwAlpcCreatePort(&hPort, &ObjAttr, NULL /*pPortAttribs*/); + if (NT_SUCCESS(rcNt)) + { + PVOID pvObject; + rcNt = ObReferenceObjectByHandle(hPort, 0 /*DesiredAccess*/, NULL /*pObjectType*/, + KernelMode, &pvObject, NULL /*pHandleInfo*/); + if (NT_SUCCESS(rcNt)) + { + POBJECT_TYPE pObjType = g_pfnObGetObjectType(pvObject); + if (pObjType) + { + SUPR0Printf("vboxdrv: ALPC Port Object Type %p (vs %p)\n", pObjType, *ppObjType); + *ppObjType = pObjType; + fDone = true; + } + ObDereferenceObject(pvObject); + } + NtClose(hPort); + } + return fDone; +} + + +/** + * Attempts to retrieve the ALPC Port object type. + * + * We've had at least three reports that using LpcPortObjectType when trying to + * get at the ApiPort object results in STATUS_OBJECT_TYPE_MISMATCH errors. + * It's not known who has modified LpcPortObjectType or AlpcPortObjectType (not + * exported) so that it differs from the actual ApiPort type, or maybe this + * unknown entity is intercepting our attempt to reference the port and + * tries to mislead us. The paranoid explanataion is of course that some evil + * root kit like software is messing with the OS, however, it's possible that + * this is valid kernel behavior that 99.8% of our users and 100% of the + * developers are not triggering for some reason. + * + * The code here creates an ALPC port object and gets it's type. It will cache + * the result in g_pAlpcPortObjectType2 on success. + * + * @returns Object type. + * @param uSessionId The session id. + * @param pszSessionId The session id formatted as a string. + */ +static POBJECT_TYPE supdrvNtProtectGetAlpcPortObjectType(uint32_t uSessionId, const char *pszSessionId) +{ + POBJECT_TYPE pObjType = *LpcPortObjectType; + + if ( g_pfnZwAlpcCreatePort + && g_pfnObGetObjectType) + { + int rc; + ssize_t cchTmp; NOREF(cchTmp); + char szTmp[16]; + RTUTF16 wszPortNm[128]; + size_t offRand; + + /* + * First attempt is in the session directory. + */ + rc = RTUtf16CopyAscii(wszPortNm, RT_ELEMENTS(wszPortNm), "\\Sessions\\"); + rc |= RTUtf16CatAscii(wszPortNm, RT_ELEMENTS(wszPortNm), pszSessionId); + rc |= RTUtf16CatAscii(wszPortNm, RT_ELEMENTS(wszPortNm), "\\VBoxDrv-"); + cchTmp = RTStrFormatU32(szTmp, sizeof(szTmp), (uint32_t)(uintptr_t)PsGetProcessId(PsGetCurrentProcess()), 16, 0, 0, 0); + Assert(cchTmp > 0); + rc |= RTUtf16CatAscii(wszPortNm, RT_ELEMENTS(wszPortNm), szTmp); + rc |= RTUtf16CatAscii(wszPortNm, RT_ELEMENTS(wszPortNm), "-"); + offRand = RTUtf16Len(wszPortNm); + cchTmp = RTStrFormatU32(szTmp, sizeof(szTmp), RTRandU32(), 16, 0, 0, 0); + Assert(cchTmp > 0); + rc |= RTUtf16CatAscii(wszPortNm, RT_ELEMENTS(wszPortNm), szTmp); + AssertRCSuccess(rc); + + bool fDone = supdrvNtProtectGetAlpcPortObjectType2(wszPortNm, &pObjType); + if (!fDone) + { + wszPortNm[offRand] = '\0'; + cchTmp = RTStrFormatU32(szTmp, sizeof(szTmp), RTRandU32(), 16, 0, 0, 0); Assert(cchTmp > 0); + rc |= RTUtf16CatAscii(wszPortNm, RT_ELEMENTS(wszPortNm), szTmp); + AssertRCSuccess(rc); + + fDone = supdrvNtProtectGetAlpcPortObjectType2(wszPortNm, &pObjType); + } + if (!fDone) + { + /* + * Try base names. + */ + if (uSessionId == 0) + rc = RTUtf16CopyAscii(wszPortNm, RT_ELEMENTS(wszPortNm), "\\BaseNamedObjects\\VBoxDrv-"); + else + { + rc = RTUtf16CopyAscii(wszPortNm, RT_ELEMENTS(wszPortNm), "\\Sessions\\"); + rc |= RTUtf16CatAscii(wszPortNm, RT_ELEMENTS(wszPortNm), pszSessionId); + rc |= RTUtf16CatAscii(wszPortNm, RT_ELEMENTS(wszPortNm), "\\BaseNamedObjects\\VBoxDrv-"); + } + cchTmp = RTStrFormatU32(szTmp, sizeof(szTmp), (uint32_t)(uintptr_t)PsGetProcessId(PsGetCurrentProcess()), 16, 0, 0, 0); + Assert(cchTmp > 0); + rc |= RTUtf16CatAscii(wszPortNm, RT_ELEMENTS(wszPortNm), szTmp); + rc |= RTUtf16CatAscii(wszPortNm, RT_ELEMENTS(wszPortNm), "-"); + offRand = RTUtf16Len(wszPortNm); + cchTmp = RTStrFormatU32(szTmp, sizeof(szTmp), RTRandU32(), 16, 0, 0, 0); + Assert(cchTmp > 0); + rc |= RTUtf16CatAscii(wszPortNm, RT_ELEMENTS(wszPortNm), szTmp); + AssertRCSuccess(rc); + + fDone = supdrvNtProtectGetAlpcPortObjectType2(wszPortNm, &pObjType); + if (!fDone) + { + wszPortNm[offRand] = '\0'; + cchTmp = RTStrFormatU32(szTmp, sizeof(szTmp), RTRandU32(), 16, 0, 0, 0); + Assert(cchTmp > 0); + rc |= RTUtf16CatAscii(wszPortNm, RT_ELEMENTS(wszPortNm), szTmp); + AssertRCSuccess(rc); + + fDone = supdrvNtProtectGetAlpcPortObjectType2(wszPortNm, &pObjType); + } + } + + /* Cache the result in g_pAlpcPortObjectType2. */ + if ( g_pAlpcPortObjectType2 == NULL + && pObjType != g_pAlpcPortObjectType1 + && fDone) + g_pAlpcPortObjectType2 = pObjType; + + } + + return pObjType; +} + + +/** + * Called in the context of VBoxDrvNtCreate to determin the CSRSS for the + * current process. + * + * The Client/Server Runtime Subsystem (CSRSS) process needs to be allowed some + * additional access right so we need to make 101% sure we correctly identify + * the CSRSS process a process is associated with. + * + * @returns IPRT status code. + * @param pNtProtect The NT protected process structure. The + * hCsrssPid member will be updated on success. + */ +static int supdrvNtProtectFindAssociatedCsrss(PSUPDRVNTPROTECT pNtProtect) +{ + Assert(pNtProtect->AvlCore.Key == PsGetCurrentProcessId()); + Assert(pNtProtect->pCsrssProcess == NULL); + Assert(pNtProtect->hCsrssPid == NULL); + + /* + * We'll try use the ApiPort LPC object for the session we're in to track + * down the CSRSS process. So, we start by constructing a path to it. + */ + int rc; + uint32_t uSessionId = PsGetProcessSessionId(PsGetCurrentProcess()); + char szSessionId[16]; + WCHAR wszApiPort[48]; + if (uSessionId == 0) + { + szSessionId[0] = '0'; + szSessionId[1] = '\0'; + rc = RTUtf16CopyAscii(wszApiPort, RT_ELEMENTS(wszApiPort), "\\Windows\\ApiPort"); + } + else + { + ssize_t cchTmp = RTStrFormatU32(szSessionId, sizeof(szSessionId), uSessionId, 10, 0, 0, 0); + AssertReturn(cchTmp > 0, (int)cchTmp); + rc = RTUtf16CopyAscii(wszApiPort, RT_ELEMENTS(wszApiPort), "\\Sessions\\"); + if (RT_SUCCESS(rc)) + rc = RTUtf16CatAscii(wszApiPort, RT_ELEMENTS(wszApiPort), szSessionId); + if (RT_SUCCESS(rc)) + rc = RTUtf16CatAscii(wszApiPort, RT_ELEMENTS(wszApiPort), "\\Windows\\ApiPort"); + } + AssertRCReturn(rc, rc); + + UNICODE_STRING ApiPortStr; + ApiPortStr.Buffer = wszApiPort; + ApiPortStr.Length = (USHORT)(RTUtf16Len(wszApiPort) * sizeof(RTUTF16)); + ApiPortStr.MaximumLength = ApiPortStr.Length + sizeof(RTUTF16); + + /* + * The object cannot be opened, but we can reference it by name. + */ + void *pvApiPortObj = NULL; + NTSTATUS rcNt = ObReferenceObjectByName(&ApiPortStr, + 0, + NULL /*pAccessState*/, + STANDARD_RIGHTS_READ, + g_pAlpcPortObjectType1, + KernelMode, + NULL /*pvParseContext*/, + &pvApiPortObj); + if ( rcNt == STATUS_OBJECT_TYPE_MISMATCH + && g_pAlpcPortObjectType2 != NULL) + rcNt = ObReferenceObjectByName(&ApiPortStr, + 0, + NULL /*pAccessState*/, + STANDARD_RIGHTS_READ, + g_pAlpcPortObjectType2, + KernelMode, + NULL /*pvParseContext*/, + &pvApiPortObj); + if ( rcNt == STATUS_OBJECT_TYPE_MISMATCH + && g_pfnObGetObjectType + && g_pfnZwAlpcCreatePort) + rcNt = ObReferenceObjectByName(&ApiPortStr, + 0, + NULL /*pAccessState*/, + STANDARD_RIGHTS_READ, + supdrvNtProtectGetAlpcPortObjectType(uSessionId, szSessionId), + KernelMode, + NULL /*pvParseContext*/, + &pvApiPortObj); + if (!NT_SUCCESS(rcNt)) + { + SUPR0Printf("vboxdrv: Error opening '%ls': %#x\n", wszApiPort, rcNt); + return rcNt == STATUS_OBJECT_TYPE_MISMATCH ? VERR_SUPDRV_APIPORT_OPEN_ERROR_TYPE : VERR_SUPDRV_APIPORT_OPEN_ERROR; + } + + /* + * Query the processes in the system so we can locate CSRSS.EXE candidates. + * Note! Attempts at using SystemSessionProcessInformation failed with + * STATUS_ACCESS_VIOLATION. + * Note! The 32 bytes on the size of to counteract the allocation header + * that rtR0MemAllocEx slaps on everything. + */ + ULONG cbNeeded = _64K - 32; + uint32_t cbBuf; + uint8_t *pbBuf = NULL; + do + { + cbBuf = RT_ALIGN(cbNeeded + _4K, _64K) - 32; + pbBuf = (uint8_t *)RTMemAlloc(cbBuf); + if (!pbBuf) + break; + + cbNeeded = 0; +#if 0 /* doesn't work. */ + SYSTEM_SESSION_PROCESS_INFORMATION Req; + Req.SessionId = uSessionId; + Req.BufferLength = cbBuf; + Req.Buffer = pbBuf; + rcNt = NtQuerySystemInformation(SystemSessionProcessInformation, &Req, sizeof(Req), &cbNeeded); +#else + rcNt = NtQuerySystemInformation(SystemProcessInformation, pbBuf, cbBuf, &cbNeeded); +#endif + if (NT_SUCCESS(rcNt)) + break; + + RTMemFree(pbBuf); + pbBuf = NULL; + } while ( rcNt == STATUS_INFO_LENGTH_MISMATCH + && cbNeeded > cbBuf + && cbNeeded < 32U*_1M); + + if ( pbBuf + && NT_SUCCESS(rcNt) + && cbNeeded >= sizeof(SYSTEM_PROCESS_INFORMATION)) + { + /* + * Walk the returned data and look for the process associated with the + * ApiPort object. The ApiPort object keeps the EPROCESS address of + * the owner process (i.e. CSRSS) relatively early in the structure. On + * 64-bit windows 8.1 it's at offset 0x18. So, obtain the EPROCESS + * pointer to likely CSRSS processes and check for a match in the first + * 0x40 bytes of the ApiPort object. + */ + rc = VERR_SUPDRV_CSRSS_NOT_FOUND; + for (uint32_t offBuf = 0; offBuf <= cbNeeded - sizeof(SYSTEM_PROCESS_INFORMATION);) + { + PRTNT_SYSTEM_PROCESS_INFORMATION pProcInfo = (PRTNT_SYSTEM_PROCESS_INFORMATION)&pbBuf[offBuf]; + if ( pProcInfo->ProcessName.Length == 9 * sizeof(WCHAR) + && pProcInfo->NumberOfThreads > 2 /* Very low guess. */ + && pProcInfo->HandleCount > 32 /* Very low guess, I hope. */ + && (uintptr_t)pProcInfo->ProcessName.Buffer - (uintptr_t)pbBuf < cbNeeded + && RT_C_TO_LOWER(pProcInfo->ProcessName.Buffer[0]) == 'c' + && RT_C_TO_LOWER(pProcInfo->ProcessName.Buffer[1]) == 's' + && RT_C_TO_LOWER(pProcInfo->ProcessName.Buffer[2]) == 'r' + && RT_C_TO_LOWER(pProcInfo->ProcessName.Buffer[3]) == 's' + && RT_C_TO_LOWER(pProcInfo->ProcessName.Buffer[4]) == 's' + && pProcInfo->ProcessName.Buffer[5] == '.' + && RT_C_TO_LOWER(pProcInfo->ProcessName.Buffer[6]) == 'e' + && RT_C_TO_LOWER(pProcInfo->ProcessName.Buffer[7]) == 'x' + && RT_C_TO_LOWER(pProcInfo->ProcessName.Buffer[8]) == 'e' ) + { + + /* Get the process structure and perform some more thorough + process checks. */ + PEPROCESS pProcess; + rcNt = PsLookupProcessByProcessId(pProcInfo->UniqueProcessId, &pProcess); + if (NT_SUCCESS(rcNt)) + { + if (supdrvNtProtectIsCsrssByProcess(pProcess)) + { + if (PsGetProcessSessionId(pProcess) == uSessionId) + { + /* Final test, check the ApiPort. + Note! The old LPC (pre Vista) objects has the PID + much earlier in the structure. Might be + worth looking for it instead. */ + bool fThatsIt = false; + __try + { + PEPROCESS *ppPortProc = (PEPROCESS *)pvApiPortObj; + uint32_t cTests = g_uNtVerCombined >= SUP_NT_VER_VISTA ? 16 : 38; /* ALPC since Vista. */ + do + { + fThatsIt = *ppPortProc == pProcess; + ppPortProc++; + } while (!fThatsIt && --cTests > 0); + } + __except(EXCEPTION_EXECUTE_HANDLER) + { + fThatsIt = false; + } + if (fThatsIt) + { + /* Ok, we found it! Keep the process structure + reference as well as the PID so we can + safely identify it later on. */ + pNtProtect->hCsrssPid = pProcInfo->UniqueProcessId; + pNtProtect->pCsrssProcess = pProcess; + rc = VINF_SUCCESS; + break; + } + } + } + + ObDereferenceObject(pProcess); + } + } + + /* Advance. */ + if (!pProcInfo->NextEntryOffset) + break; + offBuf += pProcInfo->NextEntryOffset; + } + } + else + rc = VERR_SUPDRV_SESSION_PROCESS_ENUM_ERROR; + RTMemFree(pbBuf); + ObDereferenceObject(pvApiPortObj); + return rc; +} + + +/** + * Checks that the given process is the CSRSS process associated with protected + * process. + * + * @returns true / false. + * @param pNtProtect The NT protection structure. + * @param pCsrss The process structure of the alleged CSRSS.EXE + * process. + */ +static bool supdrvNtProtectIsAssociatedCsrss(PSUPDRVNTPROTECT pNtProtect, PEPROCESS pCsrss) +{ + if (pNtProtect->pCsrssProcess == pCsrss) + { + if (pNtProtect->hCsrssPid == PsGetProcessId(pCsrss)) + { + return true; + } + } + return false; +} + + +/** + * Checks if the given process is the stupid themes service. + * + * The caller does some screening of access masks and what not. We do the rest. + * + * @returns true / false. + * @param pNtProtect The NT protection structure. + * @param pAnnoyingProcess The process structure of an process that might + * happen to be the annoying themes process. + */ +static bool supdrvNtProtectIsFrigginThemesService(PSUPDRVNTPROTECT pNtProtect, PEPROCESS pAnnoyingProcess) +{ + RT_NOREF1(pNtProtect); + + /* + * Check the process name. + */ + if (!supdrvNtProtectIsSystem32ProcessMatch(pAnnoyingProcess, "svchost.exe")) + return false; + + /** @todo Come up with more checks. */ + + return true; +} + + +#ifdef VBOX_WITHOUT_DEBUGGER_CHECKS +/** + * Checks if the given process is one of the whitelisted debuggers. + * + * @returns true / false. + * @param pProcess The process to check. + */ +static bool supdrvNtProtectIsWhitelistedDebugger(PEPROCESS pProcess) +{ + const char *pszImageFile = (const char *)PsGetProcessImageFileName(pProcess); + if (!pszImageFile) + return false; + + if (pszImageFile[0] == 'w' || pszImageFile[0] == 'W') + { + if (RTStrICmp(pszImageFile, "windbg.exe") == 0) + return true; + if (RTStrICmp(pszImageFile, "werfault.exe") == 0) + return true; + if (RTStrICmp(pszImageFile, "werfaultsecure.exe") == 0) + return true; + } + else if (pszImageFile[0] == 'd' || pszImageFile[0] == 'D') + { + if (RTStrICmp(pszImageFile, "drwtsn32.exe") == 0) + return true; + if (RTStrICmp(pszImageFile, "dwwin.exe") == 0) + return true; + } + + return false; +} +#endif /* VBOX_WITHOUT_DEBUGGER_CHECKS */ + + +/** @} */ + + +/** @name Process Creation Callbacks. + * @{ */ + + +/** + * Cleans up VBoxDrv or VBoxDrvStub error info not collected by the dead process. + * + * @param hProcessId The ID of the dead process. + */ +static void supdrvNtErrorInfoCleanupProcess(HANDLE hProcessId) +{ + int rc = RTSemMutexRequestNoResume(g_hErrorInfoLock, RT_INDEFINITE_WAIT); + if (RT_SUCCESS(rc)) + { + PSUPDRVNTERRORINFO pCur, pNext; + RTListForEachSafe(&g_ErrorInfoHead, pCur, pNext, SUPDRVNTERRORINFO, ListEntry) + { + if (pCur->hProcessId == hProcessId) + { + RTListNodeRemove(&pCur->ListEntry); + RTMemFree(pCur); + } + } + RTSemMutexRelease(g_hErrorInfoLock); + } +} + + +/** + * Common worker used by the process creation hooks as well as the process + * handle creation hooks to check if a VM process is being created. + * + * @returns true if likely to be a VM process, false if not. + * @param pNtStub The NT protection structure for the possible + * stub process. + * @param hParentPid The parent pid. + * @param hChildPid The child pid. + */ +static bool supdrvNtProtectIsSpawningStubProcess(PSUPDRVNTPROTECT pNtStub, HANDLE hParentPid, HANDLE hChildPid) +{ + bool fRc = false; + if (pNtStub->AvlCore.Key == hParentPid) /* paranoia */ + { + if (pNtStub->enmProcessKind == kSupDrvNtProtectKind_StubSpawning) + { + /* Compare short names. */ + PEPROCESS pStubProcess; + NTSTATUS rcNt = PsLookupProcessByProcessId(hParentPid, &pStubProcess); + if (NT_SUCCESS(rcNt)) + { + PEPROCESS pChildProcess; + rcNt = PsLookupProcessByProcessId(hChildPid, &pChildProcess); + if (NT_SUCCESS(rcNt)) + { + const char *pszStub = (const char *)PsGetProcessImageFileName(pStubProcess); + const char *pszChild = (const char *)PsGetProcessImageFileName(pChildProcess); + fRc = pszStub != NULL + && pszChild != NULL + && strcmp(pszStub, pszChild) == 0; + + /** @todo check that the full image names matches. */ + + ObDereferenceObject(pChildProcess); + } + ObDereferenceObject(pStubProcess); + } + } + } + return fRc; +} + + +/** + * Common code used by the notifies to protect a child process. + * + * @returns VBox status code. + * @param pNtStub The NT protect structure for the parent. + * @param hChildPid The child pid. + */ +static int supdrvNtProtectProtectNewStubChild(PSUPDRVNTPROTECT pNtParent, HANDLE hChildPid) +{ + /* + * Create a child protection struction. + */ + PSUPDRVNTPROTECT pNtChild; + int rc = supdrvNtProtectCreate(&pNtChild, hChildPid, kSupDrvNtProtectKind_VmProcessUnconfirmed, false /*fLink*/); + if (RT_SUCCESS(rc)) + { + pNtChild->fFirstProcessCreateHandle = true; + pNtChild->fFirstThreadCreateHandle = true; + pNtChild->fCsrssFirstProcessCreateHandle = true; + pNtChild->cCsrssFirstProcessDuplicateHandle = ARCH_BITS == 32 ? 2 : 1; + pNtChild->fThemesFirstProcessCreateHandle = true; + pNtChild->hParentPid = pNtParent->AvlCore.Key; + pNtChild->hCsrssPid = pNtParent->hCsrssPid; + pNtChild->pCsrssProcess = pNtParent->pCsrssProcess; + if (pNtChild->pCsrssProcess) + ObReferenceObject(pNtChild->pCsrssProcess); + + /* + * Take the spinlock, recheck parent conditions and link things. + */ + RTSpinlockAcquire(g_hNtProtectLock); + if (pNtParent->enmProcessKind == kSupDrvNtProtectKind_StubSpawning) + { + bool fSuccess = RTAvlPVInsert(&g_NtProtectTree, &pNtChild->AvlCore); + if (fSuccess) + { + pNtChild->fInTree = true; + pNtParent->u.pChild = pNtChild; /* Parent keeps the initial reference. */ + pNtParent->enmProcessKind = kSupDrvNtProtectKind_StubParent; + pNtChild->u.pParent = pNtParent; + + RTSpinlockRelease(g_hNtProtectLock); + return VINF_SUCCESS; + } + + rc = VERR_INTERNAL_ERROR_2; + } + else + rc = VERR_WRONG_ORDER; + pNtChild->enmProcessKind = kSupDrvNtProtectKind_VmProcessDead; + RTSpinlockRelease(g_hNtProtectLock); + + supdrvNtProtectRelease(pNtChild); + } + return rc; +} + + +/** + * Common process termination code. + * + * Transitions protected process to the dead states, protecting against handle + * PID reuse (esp. with unconfirmed VM processes) and handle cleanup issues. + * + * @param hDeadPid The PID of the dead process. + */ +static void supdrvNtProtectUnprotectDeadProcess(HANDLE hDeadPid) +{ + PSUPDRVNTPROTECT pNtProtect = supdrvNtProtectLookup(hDeadPid); + if (pNtProtect) + { + PSUPDRVNTPROTECT pNtChild = NULL; + + RTSpinlockAcquire(g_hNtProtectLock); + + /* + * If this is an unconfirmed VM process, we must release the reference + * the parent structure holds. + */ + if (pNtProtect->enmProcessKind == kSupDrvNtProtectKind_VmProcessUnconfirmed) + { + PSUPDRVNTPROTECT pNtParent = pNtProtect->u.pParent; + AssertRelease(pNtParent); AssertRelease(pNtParent->u.pChild == pNtProtect); + pNtParent->u.pChild = NULL; + pNtProtect->u.pParent = NULL; + pNtChild = pNtProtect; + } + /* + * If this is a stub exitting before the VM process gets confirmed, + * release the protection of the potential VM process as this is not + * the prescribed behavior. + */ + else if ( pNtProtect->enmProcessKind == kSupDrvNtProtectKind_StubParent + && pNtProtect->u.pChild) + { + pNtChild = pNtProtect->u.pChild; + pNtProtect->u.pChild = NULL; + pNtChild->u.pParent = NULL; + pNtChild->enmProcessKind = kSupDrvNtProtectKind_VmProcessDead; + } + + /* + * Transition it to the dead state to prevent it from opening the + * support driver again or be posthumously abused as a vm process parent. + */ + if ( pNtProtect->enmProcessKind == kSupDrvNtProtectKind_VmProcessUnconfirmed + || pNtProtect->enmProcessKind == kSupDrvNtProtectKind_VmProcessConfirmed) + pNtProtect->enmProcessKind = kSupDrvNtProtectKind_VmProcessDead; + else if ( pNtProtect->enmProcessKind == kSupDrvNtProtectKind_StubParent + || pNtProtect->enmProcessKind == kSupDrvNtProtectKind_StubSpawning + || pNtProtect->enmProcessKind == kSupDrvNtProtectKind_StubUnverified) + pNtProtect->enmProcessKind = kSupDrvNtProtectKind_StubDead; + + RTSpinlockRelease(g_hNtProtectLock); + + supdrvNtProtectRelease(pNtProtect); + supdrvNtProtectRelease(pNtChild); + + /* + * Do session cleanups. + */ + AssertReturnVoid((HANDLE)(uintptr_t)RTProcSelf() == hDeadPid); + if (g_pDevObjSys) + { + PSUPDRVDEVEXT pDevExt = (PSUPDRVDEVEXT)g_pDevObjSys->DeviceExtension; + PSUPDRVSESSION pSession = supdrvSessionHashTabLookup(pDevExt, (RTPROCESS)(uintptr_t)hDeadPid, + RTR0ProcHandleSelf(), NULL); + if (pSession) + { + supdrvSessionHashTabRemove(pDevExt, pSession, NULL); + supdrvSessionRelease(pSession); /* Drops the reference from supdrvSessionHashTabLookup. */ + } + } + } +} + + +/** + * Common worker for the process creation callback that verifies a new child + * being created by the handle creation callback code. + * + * @param pNtStub The parent. + * @param pNtVm The child. + * @param fCallerChecks The result of any additional tests the caller made. + * This is in order to avoid duplicating the failure + * path code. + */ +static void supdrvNtProtectVerifyNewChildProtection(PSUPDRVNTPROTECT pNtStub, PSUPDRVNTPROTECT pNtVm, bool fCallerChecks) +{ + if ( fCallerChecks + && pNtStub->enmProcessKind == kSupDrvNtProtectKind_StubParent + && pNtVm->enmProcessKind == kSupDrvNtProtectKind_VmProcessUnconfirmed + && pNtVm->u.pParent == pNtStub + && pNtStub->u.pChild == pNtVm) + { + /* Fine, reset the CSRSS hack (fixes ViRobot APT Shield 2.0 issue). */ + pNtVm->fFirstProcessCreateHandle = true; + return; + } + + LogRel(("vboxdrv: Misdetected vm stub; hParentPid=%p hChildPid=%p\n", pNtStub->AvlCore.Key, pNtVm->AvlCore.Key)); + if (pNtStub->enmProcessKind != kSupDrvNtProtectKind_VmProcessConfirmed) + supdrvNtProtectUnprotectDeadProcess(pNtVm->AvlCore.Key); +} + + +/** + * Old style callback (since forever). + * + * @param hParentPid The parent PID. + * @param hNewPid The PID of the new child. + * @param fCreated TRUE if it's a creation notification, + * FALSE if termination. + * @remarks ASSUMES this arrives before the handle creation callback. + */ +static VOID __stdcall +supdrvNtProtectCallback_ProcessCreateNotify(HANDLE hParentPid, HANDLE hNewPid, BOOLEAN fCreated) +{ + /* + * Is it a new process that needs protection? + */ + if (fCreated) + { + PSUPDRVNTPROTECT pNtStub = supdrvNtProtectLookup(hParentPid); + if (pNtStub) + { + PSUPDRVNTPROTECT pNtVm = supdrvNtProtectLookup(hNewPid); + if (!pNtVm) + { + if (supdrvNtProtectIsSpawningStubProcess(pNtStub, hParentPid, hNewPid)) + supdrvNtProtectProtectNewStubChild(pNtStub, hNewPid); + } + else + { + supdrvNtProtectVerifyNewChildProtection(pNtStub, pNtVm, true); + supdrvNtProtectRelease(pNtVm); + } + supdrvNtProtectRelease(pNtStub); + } + } + /* + * Process termination, do clean ups. + */ + else + { + supdrvNtProtectUnprotectDeadProcess(hNewPid); + supdrvNtErrorInfoCleanupProcess(hNewPid); + } +} + + +/** + * New style callback (Vista SP1+ / w2k8). + * + * @param pNewProcess The new process. + * @param hNewPid The PID of the new process. + * @param pInfo Process creation details. NULL if process + * termination notification. + * @remarks ASSUMES this arrives before the handle creation callback. + */ +static VOID __stdcall +supdrvNtProtectCallback_ProcessCreateNotifyEx(PEPROCESS pNewProcess, HANDLE hNewPid, PPS_CREATE_NOTIFY_INFO pInfo) +{ + RT_NOREF1(pNewProcess); + + /* + * Is it a new process that needs protection? + */ + if (pInfo) + { + PSUPDRVNTPROTECT pNtStub = supdrvNtProtectLookup(pInfo->CreatingThreadId.UniqueProcess); + + Log(("vboxdrv/NewProcessEx: ctx=%04zx/%p pid=%04zx ppid=%04zx ctor=%04zx/%04zx rcNt=%#x %.*ls\n", + PsGetProcessId(PsGetCurrentProcess()), PsGetCurrentProcess(), + hNewPid, pInfo->ParentProcessId, + pInfo->CreatingThreadId.UniqueProcess, pInfo->CreatingThreadId.UniqueThread, pInfo->CreationStatus, + pInfo->FileOpenNameAvailable && pInfo->ImageFileName ? (size_t)pInfo->ImageFileName->Length / 2 : 0, + pInfo->FileOpenNameAvailable && pInfo->ImageFileName ? pInfo->ImageFileName->Buffer : NULL)); + + if (pNtStub) + { + PSUPDRVNTPROTECT pNtVm = supdrvNtProtectLookup(hNewPid); + if (!pNtVm) + { + /* Parent must be creator. */ + if (pInfo->CreatingThreadId.UniqueProcess == pInfo->ParentProcessId) + { + if (supdrvNtProtectIsSpawningStubProcess(pNtStub, pInfo->ParentProcessId, hNewPid)) + supdrvNtProtectProtectNewStubChild(pNtStub, hNewPid); + } + } + else + { + /* Parent must be creator (as above). */ + supdrvNtProtectVerifyNewChildProtection(pNtStub, pNtVm, + pInfo->CreatingThreadId.UniqueProcess == pInfo->ParentProcessId); + supdrvNtProtectRelease(pNtVm); + } + supdrvNtProtectRelease(pNtStub); + } + } + /* + * Process termination, do clean ups. + */ + else + { + supdrvNtProtectUnprotectDeadProcess(hNewPid); + supdrvNtErrorInfoCleanupProcess(hNewPid); + } +} + +/** @} */ + + +/** @name Process Handle Callbacks. + * @{ */ + +/** Process rights that we allow for handles to stub and VM processes. */ +# define SUPDRV_NT_ALLOW_PROCESS_RIGHTS \ + ( PROCESS_TERMINATE \ + | PROCESS_VM_READ \ + | PROCESS_QUERY_INFORMATION \ + | PROCESS_QUERY_LIMITED_INFORMATION \ + | PROCESS_SUSPEND_RESUME \ + | DELETE \ + | READ_CONTROL \ + | SYNCHRONIZE) + +/** Evil process rights. */ +# define SUPDRV_NT_EVIL_PROCESS_RIGHTS \ + ( PROCESS_CREATE_THREAD \ + | PROCESS_SET_SESSIONID /*?*/ \ + | PROCESS_VM_OPERATION \ + | PROCESS_VM_WRITE \ + | PROCESS_DUP_HANDLE \ + | PROCESS_CREATE_PROCESS /*?*/ \ + | PROCESS_SET_QUOTA /*?*/ \ + | PROCESS_SET_INFORMATION \ + | PROCESS_SET_LIMITED_INFORMATION /*?*/ \ + | 0) +AssertCompile((SUPDRV_NT_ALLOW_PROCESS_RIGHTS & SUPDRV_NT_EVIL_PROCESS_RIGHTS) == 0); + + +static OB_PREOP_CALLBACK_STATUS __stdcall +supdrvNtProtectCallback_ProcessHandlePre(PVOID pvUser, POB_PRE_OPERATION_INFORMATION pOpInfo) +{ + Assert(pvUser == NULL); RT_NOREF1(pvUser); + Assert(pOpInfo->Operation == OB_OPERATION_HANDLE_CREATE || pOpInfo->Operation == OB_OPERATION_HANDLE_DUPLICATE); + Assert(pOpInfo->ObjectType == *PsProcessType); + + /* + * Protected? Kludge required for NtOpenProcess calls comming in before + * the create process hook triggers on Windows 8.1 (possibly others too). + */ + HANDLE hObjPid = PsGetProcessId((PEPROCESS)pOpInfo->Object); + PSUPDRVNTPROTECT pNtProtect = supdrvNtProtectLookup(hObjPid); + if (!pNtProtect) + { + HANDLE hParentPid = PsGetProcessInheritedFromUniqueProcessId((PEPROCESS)pOpInfo->Object); + PSUPDRVNTPROTECT pNtStub = supdrvNtProtectLookup(hParentPid); + if (pNtStub) + { + if (supdrvNtProtectIsSpawningStubProcess(pNtStub, hParentPid, hObjPid)) + { + supdrvNtProtectProtectNewStubChild(pNtStub, hObjPid); + pNtProtect = supdrvNtProtectLookup(hObjPid); + } + supdrvNtProtectRelease(pNtStub); + } + } + pOpInfo->CallContext = pNtProtect; /* Just for reference. */ + if (pNtProtect) + { + /* + * Ok, it's a protected process. Strip rights as required or possible. + */ + static ACCESS_MASK const s_fCsrssStupidDesires = 0x1fffff; + ACCESS_MASK fAllowedRights = SUPDRV_NT_ALLOW_PROCESS_RIGHTS; + + if (pOpInfo->Operation == OB_OPERATION_HANDLE_CREATE) + { + /* Don't restrict the process accessing itself. */ + if ((PEPROCESS)pOpInfo->Object == PsGetCurrentProcess()) + { + pOpInfo->CallContext = NULL; /* don't assert */ + pNtProtect->fFirstProcessCreateHandle = false; + + Log(("vboxdrv/ProcessHandlePre: %sctx=%04zx/%p wants %#x to %p in pid=%04zx [%d] %s\n", + pOpInfo->KernelHandle ? "k" : "", PsGetProcessId(PsGetCurrentProcess()), PsGetCurrentProcess(), + pOpInfo->Parameters->CreateHandleInformation.DesiredAccess, + pOpInfo->Object, pNtProtect->AvlCore.Key, pNtProtect->enmProcessKind, + PsGetProcessImageFileName(PsGetCurrentProcess()) )); + } +#ifdef VBOX_WITHOUT_DEBUGGER_CHECKS + /* Allow debuggers full access. */ + else if (supdrvNtProtectIsWhitelistedDebugger(PsGetCurrentProcess())) + { + pOpInfo->CallContext = NULL; /* don't assert */ + pNtProtect->fFirstProcessCreateHandle = false; + + Log(("vboxdrv/ProcessHandlePre: %sctx=%04zx/%p wants %#x to %p in pid=%04zx [%d] %s [debugger]\n", + pOpInfo->KernelHandle ? "k" : "", PsGetProcessId(PsGetCurrentProcess()), PsGetCurrentProcess(), + pOpInfo->Parameters->CreateHandleInformation.DesiredAccess, + pOpInfo->Object, pNtProtect->AvlCore.Key, pNtProtect->enmProcessKind, + PsGetProcessImageFileName(PsGetCurrentProcess()) )); + } +#endif + else + { + ACCESS_MASK const fDesiredAccess = pOpInfo->Parameters->CreateHandleInformation.DesiredAccess; + + /* Special case 1 on Vista, 7 & 8: + The CreateProcess code passes the handle over to CSRSS.EXE + and the code inBaseSrvCreateProcess will duplicate the + handle with 0x1fffff as access mask. NtDuplicateObject will + fail this call before it ever gets down here. + + Special case 2 on 8.1: + The CreateProcess code requires additional rights for + something, we'll drop these in the stub code. */ + if ( pNtProtect->enmProcessKind == kSupDrvNtProtectKind_VmProcessUnconfirmed + && pNtProtect->fFirstProcessCreateHandle + && pOpInfo->KernelHandle == 0 + && pNtProtect->hParentPid == PsGetProcessId(PsGetCurrentProcess()) + && ExGetPreviousMode() != KernelMode) + { + if ( !pOpInfo->KernelHandle + && fDesiredAccess == s_fCsrssStupidDesires) + { + if (g_uNtVerCombined < SUP_MAKE_NT_VER_SIMPLE(6, 3)) + fAllowedRights |= s_fCsrssStupidDesires; + else + fAllowedRights = fAllowedRights + | PROCESS_VM_OPERATION + | PROCESS_VM_WRITE + | PROCESS_SET_INFORMATION + | PROCESS_SET_LIMITED_INFORMATION + | 0; + pOpInfo->CallContext = NULL; /* don't assert this. */ + } + pNtProtect->fFirstProcessCreateHandle = false; + } + + /* Special case 3 on 8.1: + The interaction between the CreateProcess code and CSRSS.EXE + has changed to the better with Windows 8.1. CSRSS.EXE no + longer duplicates the process (thread too) handle, but opens + it, thus allowing us to do our job. */ + if ( g_uNtVerCombined >= SUP_MAKE_NT_VER_SIMPLE(6, 3) + && pNtProtect->enmProcessKind == kSupDrvNtProtectKind_VmProcessUnconfirmed + && pNtProtect->fCsrssFirstProcessCreateHandle + && pOpInfo->KernelHandle == 0 + && ExGetPreviousMode() == UserMode + && supdrvNtProtectIsAssociatedCsrss(pNtProtect, PsGetCurrentProcess()) ) + { + pNtProtect->fCsrssFirstProcessCreateHandle = false; + if (fDesiredAccess == s_fCsrssStupidDesires) + { + /* Not needed: PROCESS_CREATE_THREAD, PROCESS_SET_SESSIONID, + PROCESS_CREATE_PROCESS */ + fAllowedRights = fAllowedRights + | PROCESS_VM_OPERATION + | PROCESS_VM_WRITE + | PROCESS_DUP_HANDLE /* Needed for CreateProcess/VBoxTestOGL. */ + | 0; + pOpInfo->CallContext = NULL; /* don't assert this. */ + } + } + + /* Special case 4, Windows 7, Vista, possibly 8, but not 8.1: + The Themes service requires PROCESS_DUP_HANDLE access to our + process or we won't get any menus and dialogs will be half + unreadable. This is _very_ unfortunate and more work will + go into making this more secure. */ + if ( g_uNtVerCombined >= SUP_MAKE_NT_VER_SIMPLE(6, 0) + && g_uNtVerCombined < SUP_MAKE_NT_VER_SIMPLE(6, 2) + && fDesiredAccess == 0x1478 /* 6.1.7600.16385 (win7_rtm.090713-1255) */ + && pNtProtect->fThemesFirstProcessCreateHandle + && pOpInfo->KernelHandle == 0 + && ExGetPreviousMode() == UserMode + && supdrvNtProtectIsFrigginThemesService(pNtProtect, PsGetCurrentProcess()) ) + { + pNtProtect->fThemesFirstProcessCreateHandle = true; /* Only once! */ + fAllowedRights |= PROCESS_DUP_HANDLE; + pOpInfo->CallContext = NULL; /* don't assert this. */ + } + + /* Special case 6a, Windows 10+: AudioDG.exe opens the process with the + PROCESS_SET_LIMITED_INFORMATION right. It seems like it need it for + some myserious and weirdly placed cpu set management of our process. + I'd love to understand what that's all about... + Currently playing safe and only grand this right, however limited, to + audiodg.exe. */ + if ( g_uNtVerCombined >= SUP_MAKE_NT_VER_SIMPLE(10, 0) + && ( fDesiredAccess == PROCESS_SET_LIMITED_INFORMATION + || fDesiredAccess == (PROCESS_SET_LIMITED_INFORMATION | PROCESS_QUERY_LIMITED_INFORMATION) /* expected fix #1 */ + || fDesiredAccess == (PROCESS_SET_LIMITED_INFORMATION | PROCESS_QUERY_INFORMATION) /* expected fix #2 */ + ) + && pOpInfo->KernelHandle == 0 + && ExGetPreviousMode() == UserMode + && supdrvNtProtectIsSystem32ProcessMatch(PsGetCurrentProcess(), "audiodg.exe") ) + { + fAllowedRights |= PROCESS_SET_LIMITED_INFORMATION; + pOpInfo->CallContext = NULL; /* don't assert this. */ + } + + Log(("vboxdrv/ProcessHandlePre: %sctx=%04zx/%p wants %#x to %p/pid=%04zx [%d], allow %#x => %#x; %s [prev=%#x]\n", + pOpInfo->KernelHandle ? "k" : "", PsGetProcessId(PsGetCurrentProcess()), PsGetCurrentProcess(), + fDesiredAccess, pOpInfo->Object, pNtProtect->AvlCore.Key, pNtProtect->enmProcessKind, + fAllowedRights, fDesiredAccess & fAllowedRights, + PsGetProcessImageFileName(PsGetCurrentProcess()), ExGetPreviousMode() )); + + pOpInfo->Parameters->CreateHandleInformation.DesiredAccess &= fAllowedRights; + } + } + else + { + /* Don't restrict the process accessing itself. */ + if ( (PEPROCESS)pOpInfo->Object == PsGetCurrentProcess() + && pOpInfo->Parameters->DuplicateHandleInformation.TargetProcess == pOpInfo->Object) + { + Log(("vboxdrv/ProcessHandlePre: ctx=%04zx/%p[%p] dup from %04zx/%p with %#x to %p in pid=%04zx [%d] %s\n", + PsGetProcessId(PsGetCurrentProcess()), PsGetCurrentProcess(), + pOpInfo->Parameters->DuplicateHandleInformation.TargetProcess, + PsGetProcessId((PEPROCESS)pOpInfo->Parameters->DuplicateHandleInformation.SourceProcess), + pOpInfo->Parameters->DuplicateHandleInformation.SourceProcess, + pOpInfo->Parameters->DuplicateHandleInformation.DesiredAccess, + pOpInfo->Object, pNtProtect->AvlCore.Key, pNtProtect->enmProcessKind, + PsGetProcessImageFileName(PsGetCurrentProcess()) )); + + pOpInfo->CallContext = NULL; /* don't assert */ + } + else + { + ACCESS_MASK const fDesiredAccess = pOpInfo->Parameters->DuplicateHandleInformation.DesiredAccess; + + /* Special case 5 on Vista, 7 & 8: + This is the CSRSS.EXE end of special case #1. */ + if ( g_uNtVerCombined < SUP_MAKE_NT_VER_SIMPLE(6, 3) + && pNtProtect->enmProcessKind == kSupDrvNtProtectKind_VmProcessUnconfirmed + && pNtProtect->cCsrssFirstProcessDuplicateHandle > 0 + && pOpInfo->KernelHandle == 0 + && fDesiredAccess == s_fCsrssStupidDesires + && pNtProtect->hParentPid + == PsGetProcessId((PEPROCESS)pOpInfo->Parameters->DuplicateHandleInformation.SourceProcess) + && pOpInfo->Parameters->DuplicateHandleInformation.TargetProcess == PsGetCurrentProcess() + && ExGetPreviousMode() == UserMode + && supdrvNtProtectIsAssociatedCsrss(pNtProtect, PsGetCurrentProcess())) + { + if (ASMAtomicDecS32(&pNtProtect->cCsrssFirstProcessDuplicateHandle) >= 0) + { + /* Not needed: PROCESS_CREATE_THREAD, PROCESS_SET_SESSIONID, + PROCESS_CREATE_PROCESS, PROCESS_DUP_HANDLE */ + fAllowedRights = fAllowedRights + | PROCESS_VM_OPERATION + | PROCESS_VM_WRITE + | PROCESS_DUP_HANDLE /* Needed for launching VBoxTestOGL. */ + | 0; + pOpInfo->CallContext = NULL; /* don't assert this. */ + } + } + + /* Special case 6b, Windows 10+: AudioDG.exe duplicates the handle it opened above. */ + if ( g_uNtVerCombined >= SUP_MAKE_NT_VER_SIMPLE(10, 0) + && ( fDesiredAccess == PROCESS_SET_LIMITED_INFORMATION + || fDesiredAccess == (PROCESS_SET_LIMITED_INFORMATION | PROCESS_QUERY_LIMITED_INFORMATION) /* expected fix #1 */ + || fDesiredAccess == (PROCESS_SET_LIMITED_INFORMATION | PROCESS_QUERY_INFORMATION) /* expected fix #2 */ + ) + && pOpInfo->KernelHandle == 0 + && ExGetPreviousMode() == UserMode + && supdrvNtProtectIsSystem32ProcessMatch(PsGetCurrentProcess(), "audiodg.exe") ) + { + fAllowedRights |= PROCESS_SET_LIMITED_INFORMATION; + pOpInfo->CallContext = NULL; /* don't assert this. */ + } + + Log(("vboxdrv/ProcessHandlePre: %sctx=%04zx/%p[%p] dup from %04zx/%p with %#x to %p in pid=%04zx [%d] %s\n", + pOpInfo->KernelHandle ? "k" : "", PsGetProcessId(PsGetCurrentProcess()), PsGetCurrentProcess(), + pOpInfo->Parameters->DuplicateHandleInformation.TargetProcess, + PsGetProcessId((PEPROCESS)pOpInfo->Parameters->DuplicateHandleInformation.SourceProcess), + pOpInfo->Parameters->DuplicateHandleInformation.SourceProcess, + fDesiredAccess, + pOpInfo->Object, pNtProtect->AvlCore.Key, pNtProtect->enmProcessKind, + PsGetProcessImageFileName(PsGetCurrentProcess()) )); + + pOpInfo->Parameters->DuplicateHandleInformation.DesiredAccess &= fAllowedRights; + } + } + supdrvNtProtectRelease(pNtProtect); + } + + return OB_PREOP_SUCCESS; +} + + +static VOID __stdcall +supdrvNtProtectCallback_ProcessHandlePost(PVOID pvUser, POB_POST_OPERATION_INFORMATION pOpInfo) +{ + Assert(pvUser == NULL); RT_NOREF1(pvUser); + Assert(pOpInfo->Operation == OB_OPERATION_HANDLE_CREATE || pOpInfo->Operation == OB_OPERATION_HANDLE_DUPLICATE); + Assert(pOpInfo->ObjectType == *PsProcessType); + + if ( pOpInfo->CallContext + && NT_SUCCESS(pOpInfo->ReturnStatus)) + { + ACCESS_MASK const fGrantedAccess = pOpInfo->Operation == OB_OPERATION_HANDLE_CREATE + ? pOpInfo->Parameters->CreateHandleInformation.GrantedAccess + : pOpInfo->Parameters->DuplicateHandleInformation.GrantedAccess; + AssertReleaseMsg( !(fGrantedAccess & ~( SUPDRV_NT_ALLOW_PROCESS_RIGHTS + | WRITE_OWNER | WRITE_DAC /* these two might be forced upon us */ + | PROCESS_UNKNOWN_4000 /* Seen set on win 8.1 */ + /*| PROCESS_UNKNOWN_8000 */ ) ) + || pOpInfo->KernelHandle, + ("GrantedAccess=%#x - we allow %#x - we did not allow %#x\n", + fGrantedAccess, SUPDRV_NT_ALLOW_PROCESS_RIGHTS, fGrantedAccess & ~SUPDRV_NT_ALLOW_PROCESS_RIGHTS)); + } +} + +# undef SUPDRV_NT_ALLOW_PROCESS_RIGHTS + +/** @} */ + + +/** @name Thread Handle Callbacks + * @{ */ + +/* From ntifs.h */ +extern "C" NTKERNELAPI PEPROCESS __stdcall IoThreadToProcess(PETHREAD); + +/** Thread rights that we allow for handles to stub and VM processes. */ +# define SUPDRV_NT_ALLOWED_THREAD_RIGHTS \ + ( THREAD_TERMINATE \ + | THREAD_GET_CONTEXT \ + | THREAD_QUERY_INFORMATION \ + | THREAD_QUERY_LIMITED_INFORMATION \ + | DELETE \ + | READ_CONTROL \ + | SYNCHRONIZE) +/** @todo consider THREAD_SET_LIMITED_INFORMATION & THREAD_RESUME */ + +/** Evil thread rights. + * @remarks THREAD_RESUME is not included as it seems to be forced upon us by + * Windows 8.1, at least for some processes. We dont' actively + * allow it though, just tollerate it when forced to. */ +# define SUPDRV_NT_EVIL_THREAD_RIGHTS \ + ( THREAD_SUSPEND_RESUME \ + | THREAD_SET_CONTEXT \ + | THREAD_SET_INFORMATION \ + | THREAD_SET_LIMITED_INFORMATION /*?*/ \ + | THREAD_SET_THREAD_TOKEN /*?*/ \ + | THREAD_IMPERSONATE /*?*/ \ + | THREAD_DIRECT_IMPERSONATION /*?*/ \ + /*| THREAD_RESUME - see remarks. */ \ + | 0) +AssertCompile((SUPDRV_NT_EVIL_THREAD_RIGHTS & SUPDRV_NT_ALLOWED_THREAD_RIGHTS) == 0); + + +static OB_PREOP_CALLBACK_STATUS __stdcall +supdrvNtProtectCallback_ThreadHandlePre(PVOID pvUser, POB_PRE_OPERATION_INFORMATION pOpInfo) +{ + Assert(pvUser == NULL); RT_NOREF1(pvUser); + Assert(pOpInfo->Operation == OB_OPERATION_HANDLE_CREATE || pOpInfo->Operation == OB_OPERATION_HANDLE_DUPLICATE); + Assert(pOpInfo->ObjectType == *PsThreadType); + + PEPROCESS pProcess = IoThreadToProcess((PETHREAD)pOpInfo->Object); + PSUPDRVNTPROTECT pNtProtect = supdrvNtProtectLookup(PsGetProcessId(pProcess)); + pOpInfo->CallContext = pNtProtect; /* Just for reference. */ + if (pNtProtect) + { + static ACCESS_MASK const s_fCsrssStupidDesires = 0x1fffff; + ACCESS_MASK fAllowedRights = SUPDRV_NT_ALLOWED_THREAD_RIGHTS; + + if (pOpInfo->Operation == OB_OPERATION_HANDLE_CREATE) + { + /* Don't restrict the process accessing its own threads. */ + if (pProcess == PsGetCurrentProcess()) + { + Log(("vboxdrv/ThreadHandlePre: %sctx=%04zx/%p wants %#x to %p in pid=%04zx [%d] self\n", + pOpInfo->KernelHandle ? "k" : "", PsGetProcessId(PsGetCurrentProcess()), PsGetCurrentProcess(), + pOpInfo->Parameters->CreateHandleInformation.DesiredAccess, + pOpInfo->Object, pNtProtect->AvlCore.Key, pNtProtect->enmProcessKind)); + pOpInfo->CallContext = NULL; /* don't assert */ + pNtProtect->fFirstThreadCreateHandle = false; + } +#ifdef VBOX_WITHOUT_DEBUGGER_CHECKS + /* Allow debuggers full access. */ + else if (supdrvNtProtectIsWhitelistedDebugger(PsGetCurrentProcess())) + { + Log(("vboxdrv/ThreadHandlePre: %sctx=%04zx/%p wants %#x to %p in pid=%04zx [%d] %s [debugger]\n", + pOpInfo->KernelHandle ? "k" : "", PsGetProcessId(PsGetCurrentProcess()), PsGetCurrentProcess(), + pOpInfo->Parameters->CreateHandleInformation.DesiredAccess, + pOpInfo->Object, pNtProtect->AvlCore.Key, pNtProtect->enmProcessKind, + PsGetProcessImageFileName(PsGetCurrentProcess()) )); + pOpInfo->CallContext = NULL; /* don't assert */ + } +#endif + else + { + /* Special case 1 on Vista, 7, 8: + The CreateProcess code passes the handle over to CSRSS.EXE + and the code inBaseSrvCreateProcess will duplicate the + handle with 0x1fffff as access mask. NtDuplicateObject will + fail this call before it ever gets down here. */ + if ( g_uNtVerCombined < SUP_MAKE_NT_VER_SIMPLE(6, 3) + && pNtProtect->enmProcessKind == kSupDrvNtProtectKind_VmProcessUnconfirmed + && pNtProtect->fFirstThreadCreateHandle + && pOpInfo->KernelHandle == 0 + && ExGetPreviousMode() == UserMode + && pNtProtect->hParentPid == PsGetProcessId(PsGetCurrentProcess()) ) + { + if ( !pOpInfo->KernelHandle + && pOpInfo->Parameters->CreateHandleInformation.DesiredAccess == s_fCsrssStupidDesires) + { + fAllowedRights |= s_fCsrssStupidDesires; + pOpInfo->CallContext = NULL; /* don't assert this. */ + } + pNtProtect->fFirstThreadCreateHandle = false; + } + + /* Special case 2 on 8.1, possibly also Vista, 7, 8: + When creating a process like VBoxTestOGL from the VM process, + CSRSS.EXE will try talk to the calling thread and, it + appears, impersonate it. We unfortunately need to allow + this or there will be no 3D support. Typical DbgPrint: + "SXS: BasepCreateActCtx() Calling csrss server failed. Status = 0xc00000a5" */ + SUPDRVNTPROTECTKIND enmProcessKind; + if ( g_uNtVerCombined >= SUP_MAKE_NT_VER_COMBINED(6, 0, 0, 0, 0) + && ( (enmProcessKind = pNtProtect->enmProcessKind) == kSupDrvNtProtectKind_VmProcessConfirmed + || enmProcessKind == kSupDrvNtProtectKind_VmProcessUnconfirmed) + && pOpInfo->KernelHandle == 0 + && ExGetPreviousMode() == UserMode + && supdrvNtProtectIsAssociatedCsrss(pNtProtect, PsGetCurrentProcess()) ) + { + fAllowedRights |= THREAD_IMPERSONATE; + fAllowedRights |= THREAD_DIRECT_IMPERSONATION; + //fAllowedRights |= THREAD_SET_LIMITED_INFORMATION; - try without this one + pOpInfo->CallContext = NULL; /* don't assert this. */ + } + + Log(("vboxdrv/ThreadHandlePre: %sctx=%04zx/%p wants %#x to %p in pid=%04zx [%d], allow %#x => %#x; %s [prev=%#x]\n", + pOpInfo->KernelHandle ? "k" : "", PsGetProcessId(PsGetCurrentProcess()), PsGetCurrentProcess(), + pOpInfo->Parameters->CreateHandleInformation.DesiredAccess, + pOpInfo->Object, pNtProtect->AvlCore.Key, pNtProtect->enmProcessKind, fAllowedRights, + pOpInfo->Parameters->CreateHandleInformation.DesiredAccess & fAllowedRights, + PsGetProcessImageFileName(PsGetCurrentProcess()), ExGetPreviousMode())); + + pOpInfo->Parameters->CreateHandleInformation.DesiredAccess &= fAllowedRights; + } + } + else + { + /* Don't restrict the process accessing its own threads. */ + if ( pProcess == PsGetCurrentProcess() + && (PEPROCESS)pOpInfo->Parameters->DuplicateHandleInformation.TargetProcess == pProcess) + { + Log(("vboxdrv/ThreadHandlePre: %sctx=%04zx/%p[%p] dup from %04zx/%p with %#x to %p in pid=%04zx [%d] self\n", + pOpInfo->KernelHandle ? "k" : "", PsGetProcessId(PsGetCurrentProcess()), PsGetCurrentProcess(), + pOpInfo->Parameters->DuplicateHandleInformation.TargetProcess, + PsGetProcessId((PEPROCESS)pOpInfo->Parameters->DuplicateHandleInformation.SourceProcess), + pOpInfo->Parameters->DuplicateHandleInformation.SourceProcess, + pOpInfo->Parameters->DuplicateHandleInformation.DesiredAccess, + pOpInfo->Object, pNtProtect->AvlCore.Key, pNtProtect->enmProcessKind, + PsGetProcessImageFileName(PsGetCurrentProcess()) )); + pOpInfo->CallContext = NULL; /* don't assert */ + } + else + { + /* Special case 3 on Vista, 7, 8: + This is the follow up to special case 1. */ + SUPDRVNTPROTECTKIND enmProcessKind; + if ( g_uNtVerCombined >= SUP_MAKE_NT_VER_COMBINED(6, 0, 0, 0, 0) + && ( (enmProcessKind = pNtProtect->enmProcessKind) == kSupDrvNtProtectKind_VmProcessConfirmed + || enmProcessKind == kSupDrvNtProtectKind_VmProcessUnconfirmed) + && pOpInfo->Parameters->DuplicateHandleInformation.TargetProcess == PsGetCurrentProcess() + && pOpInfo->KernelHandle == 0 + && ExGetPreviousMode() == UserMode + && supdrvNtProtectIsAssociatedCsrss(pNtProtect, PsGetCurrentProcess()) ) + { + fAllowedRights |= THREAD_IMPERSONATE; + fAllowedRights |= THREAD_DIRECT_IMPERSONATION; + //fAllowedRights |= THREAD_SET_LIMITED_INFORMATION; - try without this one + pOpInfo->CallContext = NULL; /* don't assert this. */ + } + + Log(("vboxdrv/ThreadHandlePre: %sctx=%04zx/%p[%p] dup from %04zx/%p with %#x to %p in pid=%04zx [%d], allow %#x => %#x; %s\n", + pOpInfo->KernelHandle ? "k" : "", PsGetProcessId(PsGetCurrentProcess()), PsGetCurrentProcess(), + pOpInfo->Parameters->DuplicateHandleInformation.TargetProcess, + PsGetProcessId((PEPROCESS)pOpInfo->Parameters->DuplicateHandleInformation.SourceProcess), + pOpInfo->Parameters->DuplicateHandleInformation.SourceProcess, + pOpInfo->Parameters->DuplicateHandleInformation.DesiredAccess, + pOpInfo->Object, pNtProtect->AvlCore.Key, pNtProtect->enmProcessKind, fAllowedRights, + pOpInfo->Parameters->DuplicateHandleInformation.DesiredAccess & fAllowedRights, + PsGetProcessImageFileName(PsGetCurrentProcess()) )); + + pOpInfo->Parameters->DuplicateHandleInformation.DesiredAccess &= fAllowedRights; + } + } + + supdrvNtProtectRelease(pNtProtect); + } + + return OB_PREOP_SUCCESS; +} + + +static VOID __stdcall +supdrvNtProtectCallback_ThreadHandlePost(PVOID pvUser, POB_POST_OPERATION_INFORMATION pOpInfo) +{ + Assert(pvUser == NULL); RT_NOREF1(pvUser); + Assert(pOpInfo->Operation == OB_OPERATION_HANDLE_CREATE || pOpInfo->Operation == OB_OPERATION_HANDLE_DUPLICATE); + Assert(pOpInfo->ObjectType == *PsThreadType); + + if ( pOpInfo->CallContext + && NT_SUCCESS(pOpInfo->ReturnStatus)) + { + ACCESS_MASK const fGrantedAccess = pOpInfo->Parameters->CreateHandleInformation.GrantedAccess; + AssertReleaseMsg( !(fGrantedAccess & ~( SUPDRV_NT_ALLOWED_THREAD_RIGHTS + | WRITE_OWNER | WRITE_DAC /* these two might be forced upon us */ + | THREAD_RESUME /* This seems to be force upon us too with 8.1. */ + ) ) + || pOpInfo->KernelHandle, + ("GrantedAccess=%#x - we allow %#x - we did not allow %#x\n", + fGrantedAccess, SUPDRV_NT_ALLOWED_THREAD_RIGHTS, fGrantedAccess & ~SUPDRV_NT_ALLOWED_THREAD_RIGHTS)); + } +} + +# undef SUPDRV_NT_ALLOWED_THREAD_RIGHTS + +/** @} */ + + +/** + * Creates a new process protection structure. + * + * @returns VBox status code. + * @param ppNtProtect Where to return the pointer to the structure + * on success. + * @param hPid The process ID of the process to protect. + * @param enmProcessKind The kind of process we're protecting. + * @param fLink Whether to link the structure into the tree. + */ +static int supdrvNtProtectCreate(PSUPDRVNTPROTECT *ppNtProtect, HANDLE hPid, SUPDRVNTPROTECTKIND enmProcessKind, bool fLink) +{ + AssertReturn(g_hNtProtectLock != NIL_RTSPINLOCK, VERR_WRONG_ORDER); + + PSUPDRVNTPROTECT pNtProtect = (PSUPDRVNTPROTECT)RTMemAllocZ(sizeof(*pNtProtect)); + if (!pNtProtect) + return VERR_NO_MEMORY; + + pNtProtect->AvlCore.Key = hPid; + pNtProtect->u32Magic = SUPDRVNTPROTECT_MAGIC; + pNtProtect->cRefs = 1; + pNtProtect->enmProcessKind = enmProcessKind; + pNtProtect->hParentPid = NULL; + pNtProtect->hOpenTid = NULL; + pNtProtect->hCsrssPid = NULL; + pNtProtect->pCsrssProcess = NULL; + + if (fLink) + { + RTSpinlockAcquire(g_hNtProtectLock); + bool fSuccess = RTAvlPVInsert(&g_NtProtectTree, &pNtProtect->AvlCore); + pNtProtect->fInTree = fSuccess; + RTSpinlockRelease(g_hNtProtectLock); + + if (!fSuccess) + { + /* Duplicate entry, fail. */ + pNtProtect->u32Magic = SUPDRVNTPROTECT_MAGIC_DEAD; + LogRel(("supdrvNtProtectCreate: Duplicate (%#x).\n", pNtProtect->AvlCore.Key)); + RTMemFree(pNtProtect); + return VERR_DUPLICATE; + } + } + + *ppNtProtect = pNtProtect; + return VINF_SUCCESS; +} + + +/** + * Releases a reference to a NT protection structure. + * + * @param pNtProtect The NT protection structure. + */ +static void supdrvNtProtectRelease(PSUPDRVNTPROTECT pNtProtect) +{ + if (!pNtProtect) + return; + AssertReturnVoid(pNtProtect->u32Magic == SUPDRVNTPROTECT_MAGIC); + + RTSpinlockAcquire(g_hNtProtectLock); + uint32_t cRefs = ASMAtomicDecU32(&pNtProtect->cRefs); + if (cRefs != 0) + RTSpinlockRelease(g_hNtProtectLock); + else + { + /* + * That was the last reference. Remove it from the tree, invalidate it + * and free the resources associated with it. Also, release any + * child/parent references related to this protection structure. + */ + ASMAtomicWriteU32(&pNtProtect->u32Magic, SUPDRVNTPROTECT_MAGIC_DEAD); + if (pNtProtect->fInTree) + { + PSUPDRVNTPROTECT pRemoved = (PSUPDRVNTPROTECT)RTAvlPVRemove(&g_NtProtectTree, pNtProtect->AvlCore.Key); + Assert(pRemoved == pNtProtect); RT_NOREF_PV(pRemoved); + pNtProtect->fInTree = false; + } + + PSUPDRVNTPROTECT pChild = NULL; + if (pNtProtect->enmProcessKind == kSupDrvNtProtectKind_StubParent) + { + pChild = pNtProtect->u.pChild; + if (pChild) + { + pNtProtect->u.pChild = NULL; + pChild->u.pParent = NULL; + pChild->enmProcessKind = kSupDrvNtProtectKind_VmProcessDead; + uint32_t cChildRefs = ASMAtomicDecU32(&pChild->cRefs); + if (!cChildRefs) + { + Assert(pChild->fInTree); + if (pChild->fInTree) + { + PSUPDRVNTPROTECT pRemovedChild = (PSUPDRVNTPROTECT)RTAvlPVRemove(&g_NtProtectTree, pChild->AvlCore.Key); + Assert(pRemovedChild == pChild); RT_NOREF_PV(pRemovedChild); + pChild->fInTree = false; + } + } + else + pChild = NULL; + } + } + else + AssertRelease(pNtProtect->enmProcessKind != kSupDrvNtProtectKind_VmProcessUnconfirmed); + + RTSpinlockRelease(g_hNtProtectLock); + + if (pNtProtect->pCsrssProcess) + { + ObDereferenceObject(pNtProtect->pCsrssProcess); + pNtProtect->pCsrssProcess = NULL; + } + + RTMemFree(pNtProtect); + if (pChild) + RTMemFree(pChild); + } +} + + +/** + * Looks up a PID in the NT protect tree. + * + * @returns Pointer to a NT protection structure (with a referenced) on success, + * NULL if not found. + * @param hPid The process ID. + */ +static PSUPDRVNTPROTECT supdrvNtProtectLookup(HANDLE hPid) +{ + RTSpinlockAcquire(g_hNtProtectLock); + PSUPDRVNTPROTECT pFound = (PSUPDRVNTPROTECT)RTAvlPVGet(&g_NtProtectTree, hPid); + if (pFound) + ASMAtomicIncU32(&pFound->cRefs); + RTSpinlockRelease(g_hNtProtectLock); + return pFound; +} + + +/** + * Validates a few facts about the stub process when the VM process opens + * vboxdrv. + * + * This makes sure the stub process is still around and that it has neither + * debugger nor extra threads in it. + * + * @returns VBox status code. + * @param pNtProtect The unconfirmed VM process currently trying to + * open vboxdrv. + * @param pErrInfo Additional error information. + */ +static int supdrvNtProtectVerifyStubForVmProcess(PSUPDRVNTPROTECT pNtProtect, PRTERRINFO pErrInfo) +{ + /* + * Grab a reference to the parent stub process. + */ + SUPDRVNTPROTECTKIND enmStub = kSupDrvNtProtectKind_Invalid; + PSUPDRVNTPROTECT pNtStub = NULL; + RTSpinlockAcquire(g_hNtProtectLock); + if (pNtProtect->enmProcessKind == kSupDrvNtProtectKind_VmProcessUnconfirmed) + { + pNtStub = pNtProtect->u.pParent; /* weak reference. */ + if (pNtStub) + { + enmStub = pNtStub->enmProcessKind; + if (enmStub == kSupDrvNtProtectKind_StubParent) + { + uint32_t cRefs = ASMAtomicIncU32(&pNtStub->cRefs); + Assert(cRefs > 0 && cRefs < 1024); RT_NOREF_PV(cRefs); + } + else + pNtStub = NULL; + } + } + RTSpinlockRelease(g_hNtProtectLock); + + /* + * We require the stub process to be present. + */ + if (!pNtStub) + return RTErrInfoSetF(pErrInfo, VERR_SUP_VP_STUB_NOT_FOUND, "Missing stub process (enmStub=%d).", enmStub); + + /* + * Open the parent process and thread so we can check for debuggers and unwanted threads. + */ + int rc; + PEPROCESS pStubProcess; + NTSTATUS rcNt = PsLookupProcessByProcessId(pNtStub->AvlCore.Key, &pStubProcess); + if (NT_SUCCESS(rcNt)) + { + HANDLE hStubProcess; + rcNt = ObOpenObjectByPointer(pStubProcess, OBJ_KERNEL_HANDLE, NULL /*PassedAccessState*/, + 0 /*DesiredAccess*/, *PsProcessType, KernelMode, &hStubProcess); + if (NT_SUCCESS(rcNt)) + { + PETHREAD pStubThread; + rcNt = PsLookupThreadByThreadId(pNtStub->hOpenTid, &pStubThread); + if (NT_SUCCESS(rcNt)) + { + HANDLE hStubThread; + rcNt = ObOpenObjectByPointer(pStubThread, OBJ_KERNEL_HANDLE, NULL /*PassedAccessState*/, + 0 /*DesiredAccess*/, *PsThreadType, KernelMode, &hStubThread); + if (NT_SUCCESS(rcNt)) + { + /* + * Do some simple sanity checking. + */ + rc = supHardNtVpDebugger(hStubProcess, pErrInfo); + if (RT_SUCCESS(rc)) + rc = supHardNtVpThread(hStubProcess, hStubThread, pErrInfo); + + /* Clean up. */ + rcNt = NtClose(hStubThread); AssertMsg(NT_SUCCESS(rcNt), ("%#x\n", rcNt)); + } + else + rc = RTErrInfoSetF(pErrInfo, VERR_SUP_VP_STUB_THREAD_OPEN_ERROR, + "Error opening stub thread %p (tid %p, pid %p): %#x", + pStubThread, pNtStub->hOpenTid, pNtStub->AvlCore.Key, rcNt); + } + else + rc = RTErrInfoSetF(pErrInfo, VERR_SUP_VP_STUB_THREAD_NOT_FOUND, + "Failed to locate thread %p in %p: %#x", pNtStub->hOpenTid, pNtStub->AvlCore.Key, rcNt); + rcNt = NtClose(hStubProcess); AssertMsg(NT_SUCCESS(rcNt), ("%#x\n", rcNt)); + } + else + rc = RTErrInfoSetF(pErrInfo, VERR_SUP_VP_STUB_OPEN_ERROR, + "Error opening stub process %p (pid %p): %#x", pStubProcess, pNtStub->AvlCore.Key, rcNt); + ObDereferenceObject(pStubProcess); + } + else + rc = RTErrInfoSetF(pErrInfo, VERR_SUP_VP_STUB_NOT_FOUND, + "Failed to locate stub process %p: %#x", pNtStub->AvlCore.Key, rcNt); + + supdrvNtProtectRelease(pNtStub); + return rc; +} + + +static const char *supdrvNtProtectHandleTypeIndexToName(ULONG idxType, char *pszName, size_t cbName) +{ + /* + * Query the object types. + */ + uint32_t cbBuf = _8K; + uint8_t *pbBuf = (uint8_t *)RTMemAllocZ(_8K); + ULONG cbNeeded = cbBuf; + NTSTATUS rcNt = NtQueryObject(NULL, ObjectTypesInformation, pbBuf, cbBuf, &cbNeeded); + while (rcNt == STATUS_INFO_LENGTH_MISMATCH) + { + cbBuf = RT_ALIGN_32(cbNeeded + 256, _64K); + RTMemFree(pbBuf); + pbBuf = (uint8_t *)RTMemAllocZ(cbBuf); + if (pbBuf) + rcNt = NtQueryObject(NULL, ObjectTypesInformation, pbBuf, cbBuf, &cbNeeded); + else + break; + } + if (NT_SUCCESS(rcNt)) + { + Assert(cbNeeded <= cbBuf); + + POBJECT_TYPES_INFORMATION pObjTypes = (OBJECT_TYPES_INFORMATION *)pbBuf; + POBJECT_TYPE_INFORMATION pCurType = &pObjTypes->FirstType; + ULONG cLeft = pObjTypes->NumberOfTypes; + while (cLeft-- > 0 && (uintptr_t)&pCurType[1] - (uintptr_t)pbBuf < cbNeeded) + { + if (pCurType->TypeIndex == idxType) + { + PCRTUTF16 const pwszSrc = pCurType->TypeName.Buffer; + AssertBreak(pwszSrc); + size_t idxName = pCurType->TypeName.Length / sizeof(RTUTF16); + AssertBreak(idxName > 0); + AssertBreak(idxName < 128); + if (idxName >= cbName) + idxName = cbName - 1; + pszName[idxName] = '\0'; + while (idxName-- > 0) + pszName[idxName] = (char )pwszSrc[idxName]; + RTMemFree(pbBuf); + return pszName; + } + + /* next */ + pCurType = (POBJECT_TYPE_INFORMATION)( (uintptr_t)pCurType->TypeName.Buffer + + RT_ALIGN_32(pCurType->TypeName.MaximumLength, sizeof(uintptr_t))); + } + } + + RTMemFree(pbBuf); + return "unknown"; +} + + +/** + * Worker for supdrvNtProtectVerifyProcess that verifies the handles to a VM + * process and its thread. + * + * @returns VBox status code. + * @param pNtProtect The NT protect structure for getting information + * about special processes. + * @param pErrInfo Where to return additional error details. + */ +static int supdrvNtProtectRestrictHandlesToProcessAndThread(PSUPDRVNTPROTECT pNtProtect, PRTERRINFO pErrInfo) +{ + /* + * What to protect. + */ + PEPROCESS pProtectedProcess = PsGetCurrentProcess(); + HANDLE hProtectedPid = PsGetProcessId(pProtectedProcess); + PETHREAD pProtectedThread = PsGetCurrentThread(); + AssertReturn(pNtProtect->AvlCore.Key == hProtectedPid, VERR_INTERNAL_ERROR_5); + + /* + * Take a snapshot of all the handles in the system. + * Note! The 32 bytes on the size of to counteract the allocation header + * that rtR0MemAllocEx slaps on everything. + */ + uint32_t cbBuf = _256K - 32; + 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 <= 32U*_1M) + { + cbBuf = RT_ALIGN_32(cbNeeded + _4K, _64K) - 32; + RTMemFree(pbBuf); + pbBuf = (uint8_t *)RTMemAlloc(cbBuf); + if (!pbBuf) + return RTErrInfoSetF(pErrInfo, VERR_NO_MEMORY, "Error allocating %zu bytes for querying handles.", cbBuf); + rcNt = NtQuerySystemInformation(SystemExtendedHandleInformation, pbBuf, cbBuf, &cbNeeded); + } + if (!NT_SUCCESS(rcNt)) + { + RTMemFree(pbBuf); + return RTErrInfoSetF(pErrInfo, RTErrConvertFromNtStatus(rcNt), + "NtQuerySystemInformation/SystemExtendedHandleInformation failed: %#x\n", rcNt); + } + } + + /* + * Walk the information and look for handles to the two objects we're protecting. + */ + int rc = VINF_SUCCESS; +# ifdef VBOX_WITHOUT_DEBUGGER_CHECKS + HANDLE idLastDebugger = (HANDLE)~(uintptr_t)0; +# endif + + uint32_t cCsrssProcessHandles = 0; + uint32_t cSystemProcessHandles = 0; + uint32_t cEvilProcessHandles = 0; + uint32_t cBenignProcessHandles = 0; + + uint32_t cCsrssThreadHandles = 0; + uint32_t cEvilThreadHandles = 0; + uint32_t cBenignThreadHandles = 0; + + uint32_t cEvilInheritableHandles = 0; + uint32_t cBenignInheritableHandles = 0; + char szTmpName[32]; + + 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) + { + const char *pszType; + SYSTEM_HANDLE_ENTRY_INFO_EX const *pHandleInfo = &pInfo->Handles[i]; + if (pHandleInfo->Object == pProtectedProcess) + { + /* Handles within the protected process are fine. */ + if ( !(pHandleInfo->GrantedAccess & SUPDRV_NT_EVIL_PROCESS_RIGHTS) + || pHandleInfo->UniqueProcessId == hProtectedPid) + { + cBenignProcessHandles++; + continue; + } + + /* CSRSS is allowed to have one evil process handle. + See the special cases in the hook code. */ + if ( cCsrssProcessHandles < 1 + && pHandleInfo->UniqueProcessId == pNtProtect->hCsrssPid) + { + cCsrssProcessHandles++; + continue; + } + + /* The system process is allowed having two open process handle in + Windows 8.1 and later, and one in earlier. This is probably a + little overly paranoid as I think we can safely trust the + system process... */ + if ( cSystemProcessHandles < (g_uNtVerCombined >= SUP_MAKE_NT_VER_SIMPLE(6, 3) ? UINT32_C(2) : UINT32_C(1)) + && pHandleInfo->UniqueProcessId == PsGetProcessId(PsInitialSystemProcess)) + { + cSystemProcessHandles++; + continue; + } + + cEvilProcessHandles++; + pszType = "process"; + } + else if (pHandleInfo->Object == pProtectedThread) + { + /* Handles within the protected process is fine. */ + if ( !(pHandleInfo->GrantedAccess & SUPDRV_NT_EVIL_THREAD_RIGHTS) + || pHandleInfo->UniqueProcessId == hProtectedPid) + { + cBenignThreadHandles++; + continue; + } + + /* CSRSS is allowed to have one evil handle to the primary thread + for LPC purposes. See the hook for special case. */ + if ( cCsrssThreadHandles < 1 + && pHandleInfo->UniqueProcessId == pNtProtect->hCsrssPid) + { + cCsrssThreadHandles++; + continue; + } + + cEvilThreadHandles++; + pszType = "thread"; + } + else if ( (pHandleInfo->HandleAttributes & OBJ_INHERIT) + && pHandleInfo->UniqueProcessId == hProtectedPid) + { + /* No handles should be marked inheritable, except files and two events. + Handles to NT 'directory' objects are especially evil, because of + KnownDlls faking. See bugref{10294} for details. + + Correlating the ObjectTypeIndex to a type is complicated, so instead + we try referecing the handle and check the type that way. So, only + file and events objects are allowed to be marked inheritable at the + moment. Add more in whitelist fashion if needed. */ + void *pvObject = NULL; + rcNt = ObReferenceObjectByHandle(pHandleInfo->HandleValue, 0, *IoFileObjectType, KernelMode, &pvObject, NULL); + if (rcNt == STATUS_OBJECT_TYPE_MISMATCH) + rcNt = ObReferenceObjectByHandle(pHandleInfo->HandleValue, 0, *ExEventObjectType, KernelMode, &pvObject, NULL); + if (NT_SUCCESS(rcNt)) + { + ObDereferenceObject(pvObject); + cBenignInheritableHandles++; + continue; + } + + if (rcNt != STATUS_OBJECT_TYPE_MISMATCH) + { + cBenignInheritableHandles++; + continue; + } + + cEvilInheritableHandles++; + pszType = supdrvNtProtectHandleTypeIndexToName(pHandleInfo->ObjectTypeIndex, szTmpName, sizeof(szTmpName)); + } + else + continue; + +# ifdef VBOX_WITHOUT_DEBUGGER_CHECKS + /* Ignore whitelisted debuggers. */ + if (pHandleInfo->UniqueProcessId == idLastDebugger) + continue; + PEPROCESS pDbgProc; + rcNt = PsLookupProcessByProcessId(pHandleInfo->UniqueProcessId, &pDbgProc); + if (NT_SUCCESS(rcNt)) + { + bool fIsDebugger = supdrvNtProtectIsWhitelistedDebugger(pDbgProc); + ObDereferenceObject(pDbgProc); + if (fIsDebugger) + { + idLastDebugger = pHandleInfo->UniqueProcessId; + continue; + } + } +# endif + + /* Found evil handle. Currently ignoring on pre-Vista. */ +# ifndef VBOX_WITH_VISTA_NO_SP + if ( g_uNtVerCombined >= SUP_NT_VER_VISTA +# else + if ( g_uNtVerCombined >= SUP_MAKE_NT_VER_COMBINED(6, 0, 6001, 0, 0) +# endif + || g_pfnObRegisterCallbacks) + { + LogRel(("vboxdrv: Found evil handle to budding VM process: pid=%p h=%p acc=%#x attr=%#x type=%s (%u)\n", + pHandleInfo->UniqueProcessId, pHandleInfo->HandleValue, + pHandleInfo->GrantedAccess, pHandleInfo->HandleAttributes, pszType, pHandleInfo->ObjectTypeIndex)); + rc = RTErrInfoAddF(pErrInfo, VERR_SUPDRV_HARDENING_EVIL_HANDLE, + *pErrInfo->pszMsg + ? "\nFound evil handle to budding VM process: pid=%p h=%p acc=%#x attr=%#x type=%s (%u)" + : "Found evil handle to budding VM process: pid=%p h=%p acc=%#x attr=%#x type=%s (%u)", + pHandleInfo->UniqueProcessId, pHandleInfo->HandleValue, + pHandleInfo->GrantedAccess, pHandleInfo->HandleAttributes, pszType, pHandleInfo->ObjectTypeIndex); + + /* Try add the process name. */ + PEPROCESS pOffendingProcess; + rcNt = PsLookupProcessByProcessId(pHandleInfo->UniqueProcessId, &pOffendingProcess); + if (NT_SUCCESS(rcNt)) + { + const char *pszName = (const char *)PsGetProcessImageFileName(pOffendingProcess); + if (pszName && *pszName) + rc = RTErrInfoAddF(pErrInfo, rc, " [%s]", pszName); + + ObDereferenceObject(pOffendingProcess); + } + } + } + + RTMemFree(pbBuf); + return rc; +} + + +/** + * Checks if the current process checks out as a VM process stub. + * + * @returns VBox status code. + * @param pNtProtect The NT protect structure. This is upgraded to a + * final protection kind (state) on success. + */ +static int supdrvNtProtectVerifyProcess(PSUPDRVNTPROTECT pNtProtect) +{ + AssertReturn(PsGetProcessId(PsGetCurrentProcess()) == pNtProtect->AvlCore.Key, VERR_INTERNAL_ERROR_3); + + /* + * Do the verification. The handle restriction checks are only preformed + * on VM processes. + */ + int rc = VINF_SUCCESS; + PSUPDRVNTERRORINFO pErrorInfo = (PSUPDRVNTERRORINFO)RTMemAllocZ(sizeof(*pErrorInfo)); + if (RT_SUCCESS(rc)) + { + pErrorInfo->hProcessId = PsGetCurrentProcessId(); + pErrorInfo->hThreadId = PsGetCurrentThreadId(); + RTERRINFO ErrInfo; + RTErrInfoInit(&ErrInfo, pErrorInfo->szErrorInfo, sizeof(pErrorInfo->szErrorInfo)); + + if (pNtProtect->enmProcessKind >= kSupDrvNtProtectKind_VmProcessUnconfirmed) + rc = supdrvNtProtectRestrictHandlesToProcessAndThread(pNtProtect, &ErrInfo); + if (RT_SUCCESS(rc)) + { + rc = supHardenedWinVerifyProcess(NtCurrentProcess(), NtCurrentThread(), SUPHARDNTVPKIND_VERIFY_ONLY, 0 /*fFlags*/, + NULL /*pcFixes*/, &ErrInfo); + if (RT_SUCCESS(rc) && pNtProtect->enmProcessKind >= kSupDrvNtProtectKind_VmProcessUnconfirmed) + rc = supdrvNtProtectVerifyStubForVmProcess(pNtProtect, &ErrInfo); + } + } + else + rc = VERR_NO_MEMORY; + + /* + * Upgrade and return. + */ + HANDLE hOpenTid = PsGetCurrentThreadId(); + RTSpinlockAcquire(g_hNtProtectLock); + + /* Stub process verficiation is pretty much straight forward. */ + if (pNtProtect->enmProcessKind == kSupDrvNtProtectKind_StubUnverified) + { + pNtProtect->enmProcessKind = RT_SUCCESS(rc) ? kSupDrvNtProtectKind_StubSpawning : kSupDrvNtProtectKind_StubDead; + pNtProtect->hOpenTid = hOpenTid; + } + /* The VM process verification is a little bit more complicated + because we need to drop the parent process reference as well. */ + else if (pNtProtect->enmProcessKind == kSupDrvNtProtectKind_VmProcessUnconfirmed) + { + AssertRelease(pNtProtect->cRefs >= 2); /* Parent + Caller */ + PSUPDRVNTPROTECT pParent = pNtProtect->u.pParent; + AssertRelease(pParent); + AssertRelease(pParent->u.pParent == pNtProtect); + AssertRelease(pParent->enmProcessKind == kSupDrvNtProtectKind_StubParent); + pParent->u.pParent = NULL; + + pNtProtect->u.pParent = NULL; + ASMAtomicDecU32(&pNtProtect->cRefs); + + if (RT_SUCCESS(rc)) + { + pNtProtect->enmProcessKind = kSupDrvNtProtectKind_VmProcessConfirmed; + pNtProtect->hOpenTid = hOpenTid; + } + else + pNtProtect->enmProcessKind = kSupDrvNtProtectKind_VmProcessDead; + } + + /* Since the stub and VM processes are only supposed to have one thread, + we're not supposed to be subject to any races from within the processes. + + There is a race between VM process verification and the stub process + exiting, though. We require the stub process to be alive until the new + VM process has made it thru the validation. So, when the stub + terminates the notification handler will change the state of both stub + and VM process to dead. + + Also, I'm not entirely certain where the process + termination notification is triggered from, so that can theorically + create a race in both cases. */ + else + { + AssertReleaseMsg( pNtProtect->enmProcessKind == kSupDrvNtProtectKind_StubDead + || pNtProtect->enmProcessKind == kSupDrvNtProtectKind_VmProcessDead, + ("enmProcessKind=%d rc=%Rrc\n", pNtProtect->enmProcessKind, rc)); + if (RT_SUCCESS(rc)) + rc = VERR_INVALID_STATE; /* There should be no races here. */ + } + + RTSpinlockRelease(g_hNtProtectLock); + + /* + * Free error info on success, keep it on failure. + */ + if (RT_SUCCESS(rc)) + RTMemFree(pErrorInfo); + else if (pErrorInfo) + { + pErrorInfo->cchErrorInfo = (uint32_t)strlen(pErrorInfo->szErrorInfo); + if (!pErrorInfo->cchErrorInfo) + pErrorInfo->cchErrorInfo = (uint32_t)RTStrPrintf(pErrorInfo->szErrorInfo, sizeof(pErrorInfo->szErrorInfo), + "supdrvNtProtectVerifyProcess: rc=%d", rc); + RTLogWriteDebugger(pErrorInfo->szErrorInfo, pErrorInfo->cchErrorInfo); + + int rc2 = RTSemMutexRequest(g_hErrorInfoLock, RT_INDEFINITE_WAIT); + if (RT_SUCCESS(rc2)) + { + pErrorInfo->uCreatedMsTs = RTTimeMilliTS(); + + /* Free old entries. */ + PSUPDRVNTERRORINFO pCur; + while ( (pCur = RTListGetFirst(&g_ErrorInfoHead, SUPDRVNTERRORINFO, ListEntry)) != NULL + && (int64_t)(pErrorInfo->uCreatedMsTs - pCur->uCreatedMsTs) > 60000 /*60sec*/) + { + RTListNodeRemove(&pCur->ListEntry); + RTMemFree(pCur); + } + + /* Insert our new entry. */ + RTListAppend(&g_ErrorInfoHead, &pErrorInfo->ListEntry); + + RTSemMutexRelease(g_hErrorInfoLock); + } + else + RTMemFree(pErrorInfo); + } + + return rc; +} + + +# ifndef VBOX_WITHOUT_DEBUGGER_CHECKS + +/** + * Checks if the current process is being debugged. + * @return @c true if debugged, @c false if not. + */ +static bool supdrvNtIsDebuggerAttached(void) +{ + return PsIsProcessBeingDebugged(PsGetCurrentProcess()) != FALSE; +} + +# endif /* !VBOX_WITHOUT_DEBUGGER_CHECKS */ + + +/** + * Terminates the hardening bits. + */ +static void supdrvNtProtectTerm(void) +{ + /* + * Stop intercepting process and thread handle creation calls. + */ + if (g_pvObCallbacksCookie) + { + g_pfnObUnRegisterCallbacks(g_pvObCallbacksCookie); + g_pvObCallbacksCookie = NULL; + } + + /* + * Stop intercepting process creation and termination notifications. + */ + NTSTATUS rcNt; + if (g_pfnPsSetCreateProcessNotifyRoutineEx) + rcNt = g_pfnPsSetCreateProcessNotifyRoutineEx(supdrvNtProtectCallback_ProcessCreateNotifyEx, TRUE /*fRemove*/); + else + rcNt = PsSetCreateProcessNotifyRoutine(supdrvNtProtectCallback_ProcessCreateNotify, TRUE /*fRemove*/); + AssertMsg(NT_SUCCESS(rcNt), ("rcNt=%#x\n", rcNt)); + + Assert(g_NtProtectTree == NULL); + + /* + * Clean up globals. + */ + RTSpinlockDestroy(g_hNtProtectLock); + g_NtProtectTree = NIL_RTSPINLOCK; + + RTSemMutexDestroy(g_hErrorInfoLock); + g_hErrorInfoLock = NIL_RTSEMMUTEX; + + PSUPDRVNTERRORINFO pCur; + while ((pCur = RTListGetFirst(&g_ErrorInfoHead, SUPDRVNTERRORINFO, ListEntry)) != NULL) + { + RTListNodeRemove(&pCur->ListEntry); + RTMemFree(pCur); + } + + supHardenedWinTermImageVerifier(); +} + +# ifdef RT_ARCH_X86 +DECLASM(void) supdrvNtQueryVirtualMemory_0xAF(void); +DECLASM(void) supdrvNtQueryVirtualMemory_0xB0(void); +DECLASM(void) supdrvNtQueryVirtualMemory_0xB1(void); +DECLASM(void) supdrvNtQueryVirtualMemory_0xB2(void); +DECLASM(void) supdrvNtQueryVirtualMemory_0xB3(void); +DECLASM(void) supdrvNtQueryVirtualMemory_0xB4(void); +DECLASM(void) supdrvNtQueryVirtualMemory_0xB5(void); +DECLASM(void) supdrvNtQueryVirtualMemory_0xB6(void); +DECLASM(void) supdrvNtQueryVirtualMemory_0xB7(void); +DECLASM(void) supdrvNtQueryVirtualMemory_0xB8(void); +DECLASM(void) supdrvNtQueryVirtualMemory_0xB9(void); +DECLASM(void) supdrvNtQueryVirtualMemory_0xBA(void); +DECLASM(void) supdrvNtQueryVirtualMemory_0xBB(void); +DECLASM(void) supdrvNtQueryVirtualMemory_0xBC(void); +DECLASM(void) supdrvNtQueryVirtualMemory_0xBD(void); +DECLASM(void) supdrvNtQueryVirtualMemory_0xBE(void); +# elif defined(RT_ARCH_AMD64) +DECLASM(void) supdrvNtQueryVirtualMemory_0x1F(void); +DECLASM(void) supdrvNtQueryVirtualMemory_0x20(void); +DECLASM(void) supdrvNtQueryVirtualMemory_0x21(void); +DECLASM(void) supdrvNtQueryVirtualMemory_0x22(void); +DECLASM(void) supdrvNtQueryVirtualMemory_0x23(void); +extern "C" NTSYSAPI NTSTATUS NTAPI ZwRequestWaitReplyPort(HANDLE, PVOID, PVOID); +# endif + + +/** + * Initalizes the hardening bits. + * + * @returns NT status code. + */ +static NTSTATUS supdrvNtProtectInit(void) +{ + /* + * Initialize the globals. + */ + + /* The NT version. */ + ULONG uMajor, uMinor, uBuild; + PsGetVersion(&uMajor, &uMinor, &uBuild, NULL); + g_uNtVerCombined = SUP_MAKE_NT_VER_COMBINED(uMajor, uMinor, uBuild, 0, 0); + + /* Resolve methods we want but isn't available everywhere. */ + UNICODE_STRING RoutineName; + + RtlInitUnicodeString(&RoutineName, L"ObGetObjectType"); + g_pfnObGetObjectType = (PFNOBGETOBJECTTYPE)MmGetSystemRoutineAddress(&RoutineName); + + RtlInitUnicodeString(&RoutineName, L"ObRegisterCallbacks"); + g_pfnObRegisterCallbacks = (PFNOBREGISTERCALLBACKS)MmGetSystemRoutineAddress(&RoutineName); + + RtlInitUnicodeString(&RoutineName, L"ObUnRegisterCallbacks"); + g_pfnObUnRegisterCallbacks = (PFNOBUNREGISTERCALLBACKS)MmGetSystemRoutineAddress(&RoutineName); + + RtlInitUnicodeString(&RoutineName, L"PsSetCreateProcessNotifyRoutineEx"); + g_pfnPsSetCreateProcessNotifyRoutineEx = (PFNPSSETCREATEPROCESSNOTIFYROUTINEEX)MmGetSystemRoutineAddress(&RoutineName); + + RtlInitUnicodeString(&RoutineName, L"PsReferenceProcessFilePointer"); + g_pfnPsReferenceProcessFilePointer = (PFNPSREFERENCEPROCESSFILEPOINTER)MmGetSystemRoutineAddress(&RoutineName); + + RtlInitUnicodeString(&RoutineName, L"PsIsProtectedProcessLight"); + g_pfnPsIsProtectedProcessLight = (PFNPSISPROTECTEDPROCESSLIGHT)MmGetSystemRoutineAddress(&RoutineName); + + RtlInitUnicodeString(&RoutineName, L"ZwAlpcCreatePort"); + g_pfnZwAlpcCreatePort = (PFNZWALPCCREATEPORT)MmGetSystemRoutineAddress(&RoutineName); + + RtlInitUnicodeString(&RoutineName, L"ZwQueryVirtualMemory"); /* Yes, using Zw version here. */ + g_pfnNtQueryVirtualMemory = (PFNNTQUERYVIRTUALMEMORY)MmGetSystemRoutineAddress(&RoutineName); + if (!g_pfnNtQueryVirtualMemory && g_uNtVerCombined < SUP_NT_VER_VISTA) + { + /* XP & W2K3 doesn't have this function exported, so we've cooked up a + few alternative in the assembly helper file that uses the code in + ZwReadFile with a different eax value. We figure the syscall number + by inspecting ZwQueryVolumeInformationFile as it's the next number. */ +# ifdef RT_ARCH_X86 + uint8_t const *pbCode = (uint8_t const *)(uintptr_t)ZwQueryVolumeInformationFile; + if (*pbCode == 0xb8) /* mov eax, dword */ + switch (*(uint32_t const *)&pbCode[1]) + { + case 0xb0: g_pfnNtQueryVirtualMemory = (PFNNTQUERYVIRTUALMEMORY)supdrvNtQueryVirtualMemory_0xAF; break; /* just in case */ + case 0xb1: g_pfnNtQueryVirtualMemory = (PFNNTQUERYVIRTUALMEMORY)supdrvNtQueryVirtualMemory_0xB0; break; /* just in case */ + case 0xb2: g_pfnNtQueryVirtualMemory = (PFNNTQUERYVIRTUALMEMORY)supdrvNtQueryVirtualMemory_0xB1; break; /* just in case */ + case 0xb3: g_pfnNtQueryVirtualMemory = (PFNNTQUERYVIRTUALMEMORY)supdrvNtQueryVirtualMemory_0xB2; break; /* XP SP3 */ + case 0xb4: g_pfnNtQueryVirtualMemory = (PFNNTQUERYVIRTUALMEMORY)supdrvNtQueryVirtualMemory_0xB2; break; /* just in case */ + case 0xb5: g_pfnNtQueryVirtualMemory = (PFNNTQUERYVIRTUALMEMORY)supdrvNtQueryVirtualMemory_0xB3; break; /* just in case */ + case 0xb6: g_pfnNtQueryVirtualMemory = (PFNNTQUERYVIRTUALMEMORY)supdrvNtQueryVirtualMemory_0xB4; break; /* just in case */ + case 0xb7: g_pfnNtQueryVirtualMemory = (PFNNTQUERYVIRTUALMEMORY)supdrvNtQueryVirtualMemory_0xB5; break; /* just in case */ + case 0xb8: g_pfnNtQueryVirtualMemory = (PFNNTQUERYVIRTUALMEMORY)supdrvNtQueryVirtualMemory_0xB6; break; /* just in case */ + case 0xb9: g_pfnNtQueryVirtualMemory = (PFNNTQUERYVIRTUALMEMORY)supdrvNtQueryVirtualMemory_0xB7; break; /* just in case */ + case 0xba: g_pfnNtQueryVirtualMemory = (PFNNTQUERYVIRTUALMEMORY)supdrvNtQueryVirtualMemory_0xB8; break; /* just in case */ + case 0xbb: g_pfnNtQueryVirtualMemory = (PFNNTQUERYVIRTUALMEMORY)supdrvNtQueryVirtualMemory_0xBA; break; /* W2K3 R2 SP2 */ + case 0xbc: g_pfnNtQueryVirtualMemory = (PFNNTQUERYVIRTUALMEMORY)supdrvNtQueryVirtualMemory_0xBB; break; /* just in case */ + case 0xbd: g_pfnNtQueryVirtualMemory = (PFNNTQUERYVIRTUALMEMORY)supdrvNtQueryVirtualMemory_0xBC; break; /* just in case */ + case 0xbe: g_pfnNtQueryVirtualMemory = (PFNNTQUERYVIRTUALMEMORY)supdrvNtQueryVirtualMemory_0xBD; break; /* just in case */ + case 0xbf: g_pfnNtQueryVirtualMemory = (PFNNTQUERYVIRTUALMEMORY)supdrvNtQueryVirtualMemory_0xBE; break; /* just in case */ + } +# elif defined(RT_ARCH_AMD64) + uint8_t const *pbCode = (uint8_t const *)(uintptr_t)ZwRequestWaitReplyPort; + if ( pbCode[ 0] == 0x48 /* mov rax, rsp */ + && pbCode[ 1] == 0x8b + && pbCode[ 2] == 0xc4 + && pbCode[ 3] == 0xfa /* cli */ + && pbCode[ 4] == 0x48 /* sub rsp, 10h */ + && pbCode[ 5] == 0x83 + && pbCode[ 6] == 0xec + && pbCode[ 7] == 0x10 + && pbCode[ 8] == 0x50 /* push rax */ + && pbCode[ 9] == 0x9c /* pushfq */ + && pbCode[10] == 0x6a /* push 10 */ + && pbCode[11] == 0x10 + && pbCode[12] == 0x48 /* lea rax, [nt!KiServiceLinkage] */ + && pbCode[13] == 0x8d + && pbCode[14] == 0x05 + && pbCode[19] == 0x50 /* push rax */ + && pbCode[20] == 0xb8 /* mov eax,1fh <- the syscall no. */ + /*&& pbCode[21] == 0x1f*/ + && pbCode[22] == 0x00 + && pbCode[23] == 0x00 + && pbCode[24] == 0x00 + && pbCode[25] == 0xe9 /* jmp KiServiceInternal */ + ) + { + uint8_t const *pbKiServiceInternal = &pbCode[30] + *(int32_t const *)&pbCode[26]; + uint8_t const *pbKiServiceLinkage = &pbCode[19] + *(int32_t const *)&pbCode[15]; + if (*pbKiServiceLinkage == 0xc3) + { + g_pfnKiServiceInternal = (PFNRT)pbKiServiceInternal; + g_pfnKiServiceLinkage = (PFNRT)pbKiServiceLinkage; + switch (pbCode[21]) + { + case 0x1e: g_pfnNtQueryVirtualMemory = (PFNNTQUERYVIRTUALMEMORY)supdrvNtQueryVirtualMemory_0x1F; break; + case 0x1f: g_pfnNtQueryVirtualMemory = (PFNNTQUERYVIRTUALMEMORY)supdrvNtQueryVirtualMemory_0x20; break; + case 0x20: g_pfnNtQueryVirtualMemory = (PFNNTQUERYVIRTUALMEMORY)supdrvNtQueryVirtualMemory_0x21; break; + case 0x21: g_pfnNtQueryVirtualMemory = (PFNNTQUERYVIRTUALMEMORY)supdrvNtQueryVirtualMemory_0x22; break; + case 0x22: g_pfnNtQueryVirtualMemory = (PFNNTQUERYVIRTUALMEMORY)supdrvNtQueryVirtualMemory_0x23; break; + } + } + } +# endif + } + if (!g_pfnNtQueryVirtualMemory) + { + LogRel(("vboxdrv: Cannot locate ZwQueryVirtualMemory in ntoskrnl, nor were we able to cook up a replacement.\n")); + return STATUS_PROCEDURE_NOT_FOUND; + } + +# ifdef VBOX_STRICT + if ( g_uNtVerCombined >= SUP_NT_VER_W70 + && ( g_pfnObGetObjectType == NULL + || g_pfnZwAlpcCreatePort == NULL) ) + { + LogRel(("vboxdrv: g_pfnObGetObjectType=%p g_pfnZwAlpcCreatePort=%p.\n", g_pfnObGetObjectType, g_pfnZwAlpcCreatePort)); + return STATUS_PROCEDURE_NOT_FOUND; + } +# endif + + /* LPC object type. */ + g_pAlpcPortObjectType1 = *LpcPortObjectType; + + /* The spinlock protecting our structures. */ + int rc = RTSpinlockCreate(&g_hNtProtectLock, RTSPINLOCK_FLAGS_INTERRUPT_UNSAFE, "NtProtectLock"); + if (RT_FAILURE(rc)) + return VBoxDrvNtErr2NtStatus(rc); + g_NtProtectTree = NULL; + + NTSTATUS rcNt; + + /* The mutex protecting the error information. */ + RTListInit(&g_ErrorInfoHead); + rc = RTSemMutexCreate(&g_hErrorInfoLock); + if (RT_SUCCESS(rc)) + { + /* Image stuff + certificates. */ + rc = supHardenedWinInitImageVerifier(NULL); + if (RT_SUCCESS(rc)) + { + /* + * Intercept process creation and termination. + */ + if (g_pfnPsSetCreateProcessNotifyRoutineEx) + rcNt = g_pfnPsSetCreateProcessNotifyRoutineEx(supdrvNtProtectCallback_ProcessCreateNotifyEx, FALSE /*fRemove*/); + else + rcNt = PsSetCreateProcessNotifyRoutine(supdrvNtProtectCallback_ProcessCreateNotify, FALSE /*fRemove*/); + if (NT_SUCCESS(rcNt)) + { + /* + * Intercept process and thread handle creation calls. + * The preferred method is only available on Vista SP1+. + */ + if (g_pfnObRegisterCallbacks && g_pfnObUnRegisterCallbacks) + { + static OB_OPERATION_REGISTRATION s_aObOperations[] = + { + { + 0, /* PsProcessType - imported, need runtime init, better do it explicitly. */ + OB_OPERATION_HANDLE_CREATE | OB_OPERATION_HANDLE_DUPLICATE, + supdrvNtProtectCallback_ProcessHandlePre, + supdrvNtProtectCallback_ProcessHandlePost, + }, + { + 0, /* PsThreadType - imported, need runtime init, better do it explicitly. */ + OB_OPERATION_HANDLE_CREATE | OB_OPERATION_HANDLE_DUPLICATE, + supdrvNtProtectCallback_ThreadHandlePre, + supdrvNtProtectCallback_ThreadHandlePost, + }, + }; + s_aObOperations[0].ObjectType = PsProcessType; + s_aObOperations[1].ObjectType = PsThreadType; + + static OB_CALLBACK_REGISTRATION s_ObCallbackReg = + { + /* .Version = */ OB_FLT_REGISTRATION_VERSION, + /* .OperationRegistrationCount = */ RT_ELEMENTS(s_aObOperations), + /* .Altitude.Length = */ { 0, + /* .Altitude.MaximumLength = */ 0, + /* .Altitude.Buffer = */ NULL }, + /* .RegistrationContext = */ NULL, + /* .OperationRegistration = */ &s_aObOperations[0] + }; + static WCHAR const *s_apwszAltitudes[] = /** @todo get a valid number */ + { + L"48596.98940", L"46935.19485", L"49739.39704", L"40334.74976", + L"66667.98940", L"69888.19485", L"69889.39704", L"60364.74976", + L"85780.98940", L"88978.19485", L"89939.39704", L"80320.74976", + L"329879.98940", L"326787.19485", L"328915.39704", L"320314.74976", + }; + + rcNt = STATUS_FLT_INSTANCE_ALTITUDE_COLLISION; + for (uint32_t i = 0; i < RT_ELEMENTS(s_apwszAltitudes) && rcNt == STATUS_FLT_INSTANCE_ALTITUDE_COLLISION; i++) + { + s_ObCallbackReg.Altitude.Buffer = (WCHAR *)s_apwszAltitudes[i]; + s_ObCallbackReg.Altitude.Length = (uint16_t)RTUtf16Len(s_apwszAltitudes[i]) * sizeof(WCHAR); + s_ObCallbackReg.Altitude.MaximumLength = s_ObCallbackReg.Altitude.Length + sizeof(WCHAR); + + rcNt = g_pfnObRegisterCallbacks(&s_ObCallbackReg, &g_pvObCallbacksCookie); + if (NT_SUCCESS(rcNt)) + { + /* + * Happy ending. + */ + return STATUS_SUCCESS; + } + } + LogRel(("vboxdrv: ObRegisterCallbacks failed with rcNt=%#x\n", rcNt)); + g_pvObCallbacksCookie = NULL; + } + else + { + /* + * For the time being, we do not implement extra process + * protection on pre-Vista-SP1 systems as they are lacking + * necessary KPIs. XP is end of life, we do not wish to + * spend more time on it, so we don't put up a fuss there. + * Vista users without SP1 can install SP1 (or later), darn it, + * so refuse to load. + */ + /** @todo Hack up an XP solution - will require hooking kernel APIs or doing bad + * stuff to a couple of object types. */ +# ifndef VBOX_WITH_VISTA_NO_SP + if (g_uNtVerCombined >= SUP_NT_VER_VISTA) +# else + if (g_uNtVerCombined >= SUP_MAKE_NT_VER_COMBINED(6, 0, 6001, 0, 0)) +# endif + { + DbgPrint("vboxdrv: ObRegisterCallbacks was not found. Please make sure you got the latest updates and service packs installed\n"); + rcNt = STATUS_SXS_VERSION_CONFLICT; + } + else + { + Log(("vboxdrv: ObRegisterCallbacks was not found; ignored pre-Vista\n")); + return rcNt = STATUS_SUCCESS; + } + g_pvObCallbacksCookie = NULL; + } + + /* + * Drop process create/term notifications. + */ + if (g_pfnPsSetCreateProcessNotifyRoutineEx) + g_pfnPsSetCreateProcessNotifyRoutineEx(supdrvNtProtectCallback_ProcessCreateNotifyEx, TRUE /*fRemove*/); + else + PsSetCreateProcessNotifyRoutine(supdrvNtProtectCallback_ProcessCreateNotify, TRUE /*fRemove*/); + } + else + LogRel(("vboxdrv: PsSetCreateProcessNotifyRoutine%s failed with rcNt=%#x\n", + g_pfnPsSetCreateProcessNotifyRoutineEx ? "Ex" : "", rcNt)); + supHardenedWinTermImageVerifier(); + } + else + rcNt = VBoxDrvNtErr2NtStatus(rc); + + RTSemMutexDestroy(g_hErrorInfoLock); + g_hErrorInfoLock = NIL_RTSEMMUTEX; + } + else + rcNt = VBoxDrvNtErr2NtStatus(rc); + + RTSpinlockDestroy(g_hNtProtectLock); + g_NtProtectTree = NIL_RTSPINLOCK; + return rcNt; +} + +#endif /* VBOX_WITH_HARDENING */ + diff --git a/src/VBox/HostDrivers/Support/win/SUPDrvA-win.asm b/src/VBox/HostDrivers/Support/win/SUPDrvA-win.asm new file mode 100644 index 00000000..f899c815 --- /dev/null +++ b/src/VBox/HostDrivers/Support/win/SUPDrvA-win.asm @@ -0,0 +1,119 @@ +; $Id: SUPDrvA-win.asm $ +;; @file +; VirtualBox Support Driver - Windows NT specific assembly parts. +; + +; +; Copyright (C) 2006-2023 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 <https://www.gnu.org/licenses>. +; +; 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 * +;******************************************************************************* +%include "iprt/asmdefs.mac" + +BEGINCODE + +%ifdef VBOX_WITH_HARDENING + + %ifdef RT_ARCH_X86 +; +; Faking up ZwQueryVirtualMemory on XP and W2K3 where it's not exported. +; Using ZwOpenFile as a helper as it has the name number of parameters. +; +extern IMPNAME(ZwOpenFile@24) + +BEGINPROC supdrvNtQueryVirtualMemory_Xxx + %macro NtQueryVirtualMemorySyscall 1 + GLOBALNAME supdrvNtQueryVirtualMemory_ %+ %1 + mov eax, %1 + jmp supdrvNtQueryVirtualMemory_Jump + %endm + NtQueryVirtualMemorySyscall 0xAF + NtQueryVirtualMemorySyscall 0xB0 + NtQueryVirtualMemorySyscall 0xB1 + NtQueryVirtualMemorySyscall 0xB2 + NtQueryVirtualMemorySyscall 0xB3 + NtQueryVirtualMemorySyscall 0xB4 + NtQueryVirtualMemorySyscall 0xB5 + NtQueryVirtualMemorySyscall 0xB6 + NtQueryVirtualMemorySyscall 0xB7 + NtQueryVirtualMemorySyscall 0xB8 + NtQueryVirtualMemorySyscall 0xB9 + NtQueryVirtualMemorySyscall 0xBA + NtQueryVirtualMemorySyscall 0xBB + NtQueryVirtualMemorySyscall 0xBC + NtQueryVirtualMemorySyscall 0xBD + NtQueryVirtualMemorySyscall 0xBE + +supdrvNtQueryVirtualMemory_Jump: + mov edx, IMP2(ZwOpenFile@24) + lea edx, [edx + 5] + jmp edx +ENDPROC supdrvNtQueryVirtualMemory_Xxx + + %endif + + %ifdef RT_ARCH_AMD64 +; +; Faking up ZwQueryVirtualMemory on XP64 and W2K3-64 where it's not exported. +; The C code locates and verifies the essentials in ZwRequestWaitReplyPort. +; +extern NAME(g_pfnKiServiceLinkage) +extern NAME(g_pfnKiServiceInternal) +BEGINPROC supdrvNtQueryVirtualMemory_Xxx + %macro NtQueryVirtualMemorySyscall 1 + GLOBALNAME supdrvNtQueryVirtualMemory_ %+ %1 + mov eax, %1 + jmp supdrvNtQueryVirtualMemory_Jump + %endm + + NtQueryVirtualMemorySyscall 0x1F + NtQueryVirtualMemorySyscall 0x20 + NtQueryVirtualMemorySyscall 0x21 + NtQueryVirtualMemorySyscall 0x22 + NtQueryVirtualMemorySyscall 0x23 + +supdrvNtQueryVirtualMemory_Jump: + cli + mov r10, rsp ; save call frame pointer. + mov r11, [NAME(g_pfnKiServiceLinkage) wrt rip] + push 0 + push 0 + push r10 ; call frame pointer (incoming rsp). + pushfq + push 10h + push r11 ; r11 = KiServiceLinkage (ret w/ unwind info) + jmp qword [NAME(g_pfnKiServiceInternal) wrt rip] +ENDPROC supdrvNtQueryVirtualMemory_Xxx + %endif + +%endif ; VBOX_WITH_HARDENING + diff --git a/src/VBox/HostDrivers/Support/win/SUPHardenedVerify-win.h b/src/VBox/HostDrivers/Support/win/SUPHardenedVerify-win.h new file mode 100644 index 00000000..0f953e38 --- /dev/null +++ b/src/VBox/HostDrivers/Support/win/SUPHardenedVerify-win.h @@ -0,0 +1,303 @@ +/* $Id: SUPHardenedVerify-win.h $ */ +/** @file + * VirtualBox Support Library/Driver - Hardened Verification, Windows. + */ + +/* + * Copyright (C) 2006-2023 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 <https://www.gnu.org/licenses>. + * + * 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 + */ + +#ifndef VBOX_INCLUDED_SRC_Support_win_SUPHardenedVerify_win_h +#define VBOX_INCLUDED_SRC_Support_win_SUPHardenedVerify_win_h +#ifndef RT_WITHOUT_PRAGMA_ONCE +# pragma once +#endif + +#include <iprt/types.h> +#include <iprt/crypto/x509.h> +#ifndef SUP_CERTIFICATES_ONLY +# ifdef RT_OS_WINDOWS +# include <iprt/ldr.h> +# endif +#endif + + +RT_C_DECLS_BEGIN + +#ifndef SUP_CERTIFICATES_ONLY +# ifdef RT_OS_WINDOWS +DECLHIDDEN(int) supHardenedWinInitImageVerifier(PRTERRINFO pErrInfo); +DECLHIDDEN(void) supHardenedWinTermImageVerifier(void); +DECLHIDDEN(void) supR3HardenedWinVerifyCacheScheduleImports(RTLDRMOD hLdrMod, PCRTUTF16 pwszName); +DECLHIDDEN(void) supR3HardenedWinVerifyCachePreload(PCRTUTF16 pwszName); + + +typedef enum SUPHARDNTVPKIND +{ + SUPHARDNTVPKIND_VERIFY_ONLY = 1, + SUPHARDNTVPKIND_CHILD_PURIFICATION, + SUPHARDNTVPKIND_SELF_PURIFICATION, + SUPHARDNTVPKIND_SELF_PURIFICATION_LIMITED, + SUPHARDNTVPKIND_32BIT_HACK = 0x7fffffff +} SUPHARDNTVPKIND; +/** @name SUPHARDNTVP_F_XXX - Flags for supHardenedWinVerifyProcess + * @{ */ +/** Replace unwanted executable memory allocations with a new one that's filled + * with a safe read-write copy (default is just to free it). + * + * This is one way we attempt to work around buggy protection software that + * either result in host BSOD or VBox application malfunction. Here the current + * shit list: + * - Trend Micro's data protection software includes a buggy driver called + * sakfile.sys that has been observed crashing accessing user memory that we + * probably freed. I'd love to report this to Trend Micro, but unfortunately + * they doesn't advertise (or have?) an email address for reporting security + * vulnerabilities in the their software. Having wasted time looking and not + * very sorry for having to disclosing the bug here. + * - Maybe one more. + */ +#define SUPHARDNTVP_F_EXEC_ALLOC_REPLACE_WITH_RW RT_BIT_32(0) +/** @} */ +DECLHIDDEN(int) supHardenedWinVerifyProcess(HANDLE hProcess, HANDLE hThread, SUPHARDNTVPKIND enmKind, + uint32_t fFlags, uint32_t *pcFixes, PRTERRINFO pErrInfo); +DECLHIDDEN(int) supHardNtVpThread(HANDLE hProcess, HANDLE hThread, PRTERRINFO pErrInfo); +DECLHIDDEN(int) supHardNtVpDebugger(HANDLE hProcess, PRTERRINFO pErrInfo); + +DECLHIDDEN(bool) supHardViUtf16PathIsEqualEx(PCRTUTF16 pawcLeft, size_t cwcLeft, const char *pszRight); +DECLHIDDEN(bool) supHardViUniStrPathStartsWithUniStr(UNICODE_STRING const *pUniStrLeft, + UNICODE_STRING const *pUniStrRight, bool fCheckSlash); +DECLHIDDEN(bool) supHardViUtf16PathStartsWithEx(PCRTUTF16 pwszLeft, uint32_t cwcLeft, + PCRTUTF16 pwszRight, uint32_t cwcRight, bool fCheckSlash); +DECLHIDDEN(bool) supHardViIsAppPatchDir(PCRTUTF16 pwszPath, uint32_t cwcName); + + +/** + * SUP image verifier loader reader instance. + */ +typedef struct SUPHNTVIRDR +{ + /** The core reader structure. */ + RTLDRREADER Core; + /** The file handle. */ + HANDLE hFile; + /** Handle to event sempahore in case we're force to deal with asynchronous I/O. */ + HANDLE hEvent; + /** Current file offset. */ + RTFOFF off; + /** The file size. */ + uint64_t cbFile; + /** Flags for the verification callback, SUPHNTVI_F_XXX. */ + uint32_t fFlags; + /** Number of signatures that verified okay. */ + uint16_t cOkaySignatures; + /** Number of signatures that couldn't be successfully verified (time stamp + * issues, no certificate path, etc) but weren't fatal. */ + uint16_t cNokSignatures; + /** Total number of signatures. */ + uint16_t cTotalSignatures; + /** The current signature (for passing to supHardNtViCertVerifyCallback). */ + uint16_t iCurSignature; + /** The last non-fatal signature failure. */ + int rcLastSignatureFailure; + /** Log name. */ + char szFilename[1]; +} SUPHNTVIRDR; +/** Pointer to an SUP image verifier loader reader instance. */ +typedef SUPHNTVIRDR *PSUPHNTVIRDR; +DECLHIDDEN(int) supHardNtViRdrCreate(HANDLE hFile, PCRTUTF16 pwszName, uint32_t fFlags, PSUPHNTVIRDR *ppNtViRdr); +DECLHIDDEN(bool) supHardenedWinIsWinVerifyTrustCallable(void); +DECLHIDDEN(int) supHardenedWinVerifyImageTrust(HANDLE hFile, PCRTUTF16 pwszName, uint32_t fFlags, int rc, + bool *pfWinVerifyTrust, PRTERRINFO pErrInfo); +DECLHIDDEN(int) supHardenedWinVerifyImageByHandle(HANDLE hFile, PCRTUTF16 pwszName, uint32_t fFlags, + bool fAvoidWinVerifyTrust, bool *pfWinVerifyTrust, PRTERRINFO pErrInfo); +DECLHIDDEN(int) supHardenedWinVerifyImageByHandleNoName(HANDLE hFile, uint32_t fFlags, PRTERRINFO pErrInfo); +DECLHIDDEN(int) supHardenedWinVerifyImageByLdrMod(RTLDRMOD hLdrMod, PCRTUTF16 pwszName, PSUPHNTVIRDR pNtViRdr, + bool fAvoidWinVerifyTrust, bool *pfWinVerifyTrust, PRTERRINFO pErrInfo); +/** @name SUPHNTVI_F_XXX - Flags for supHardenedWinVerifyImageByHandle. + * @{ */ +/** The signing certificate must be the same as the one the VirtualBox build + * was signed with. */ +# define SUPHNTVI_F_REQUIRE_BUILD_CERT RT_BIT(0) +/** Require kernel code signing level. */ +# define SUPHNTVI_F_REQUIRE_KERNEL_CODE_SIGNING RT_BIT(1) +/** Require the image to force the memory mapper to do signature checking. */ +# define SUPHNTVI_F_REQUIRE_SIGNATURE_ENFORCEMENT RT_BIT(2) +/** Whether to allow image verification by catalog file. */ +# define SUPHNTVI_F_ALLOW_CAT_FILE_VERIFICATION RT_BIT(3) +/** The file owner must be TrustedInstaller on Vista+. */ +# define SUPHNTVI_F_TRUSTED_INSTALLER_OWNER RT_BIT(4) +/** Ignore the image architecture (otherwise it must match the verification + * code). Used with resource images and such. */ +# define SUPHNTVI_F_IGNORE_ARCHITECTURE RT_BIT(30) +/** Raw-mode context image, always 32-bit. */ +# define SUPHNTVI_F_RC_IMAGE RT_BIT(31) +/** @} */ + +/* Array in SUPHardenedVerifyImage-win.cpp */ +extern const RTSTRTUPLE g_aSupNtViBlacklistedDlls[]; + +/** + * Loader cache entry. + * + * This is for avoiding loading and signature checking a file multiple times, + * due to multiple passes thru the process validation code (and syscall import + * code of NTDLL). + */ +typedef struct SUPHNTLDRCACHEENTRY +{ + /** The file name (from g_apszSupNtVpAllowedDlls or + * g_apszSupNtVpAllowedVmExes). */ + const char *pszName; + /** Load module associated with the image during content verfication. */ + RTLDRMOD hLdrMod; + /** The file reader. */ + PSUPHNTVIRDR pNtViRdr; + /** The module file handle, if we've opened it. + * (pNtviRdr does not close the file handle on destruction.) */ + HANDLE hFile; + /** Bits buffer. */ + uint8_t *pbBits; + /** Set if verified. */ + bool fVerified; + /** Whether we've got valid cacheable image bits. */ + bool fValidBits; + /** The image base address. */ + uintptr_t uImageBase; +} SUPHNTLDRCACHEENTRY; +/** Pointer to a loader cache entry. */ +typedef SUPHNTLDRCACHEENTRY *PSUPHNTLDRCACHEENTRY; +DECLHIDDEN(int) supHardNtLdrCacheOpen(const char *pszName, PSUPHNTLDRCACHEENTRY *ppEntry, PRTERRINFO pErrInfo); +DECLHIDDEN(int) supHardNtLdrCacheEntryVerify(PSUPHNTLDRCACHEENTRY pEntry, PCRTUTF16 pwszName, PRTERRINFO pErrInfo); +DECLHIDDEN(int) supHardNtLdrCacheEntryGetBits(PSUPHNTLDRCACHEENTRY pEntry, uint8_t **ppbBits, RTLDRADDR uBaseAddress, + PFNRTLDRIMPORT pfnGetImport, void *pvUser, PRTERRINFO pErrInfo); + + +/** Which directory under the system root to get. */ +typedef enum SUPHARDNTSYSROOTDIR +{ + kSupHardNtSysRootDir_System32 = 0, + kSupHardNtSysRootDir_WinSxS, +} SUPHARDNTSYSROOTDIR; + +DECLHIDDEN(int) supHardNtGetSystemRootDir(void *pvBuf, uint32_t cbBuf, SUPHARDNTSYSROOTDIR enmDir, PRTERRINFO pErrInfo); + +# ifndef SUPHNTVI_NO_NT_STUFF + +/** Typical system root directory buffer. */ +typedef struct SUPSYSROOTDIRBUF +{ + UNICODE_STRING UniStr; + WCHAR awcBuffer[260]; +} SUPSYSROOTDIRBUF; +extern SUPSYSROOTDIRBUF g_System32NtPath; +extern SUPSYSROOTDIRBUF g_WinSxSNtPath; +#if defined(IN_RING3) && !defined(VBOX_PERMIT_EVEN_MORE) +extern SUPSYSROOTDIRBUF g_ProgramFilesNtPath; +extern SUPSYSROOTDIRBUF g_CommonFilesNtPath; +# if ARCH_BITS == 64 +extern SUPSYSROOTDIRBUF g_ProgramFilesX86NtPath; +extern SUPSYSROOTDIRBUF g_CommonFilesX86NtPath; +# endif +#endif /* IN_RING3 && !VBOX_PERMIT_EVEN_MORE */ +extern SUPSYSROOTDIRBUF g_SupLibHardenedExeNtPath; +extern SUPSYSROOTDIRBUF g_SupLibHardenedAppBinNtPath; + +# ifdef IN_RING0 +/** Pointer to NtQueryVirtualMemory. */ +typedef DECLCALLBACKPTR_EX(NTSTATUS, NTAPI, PFNNTQUERYVIRTUALMEMORY,(HANDLE, void const *, MEMORY_INFORMATION_CLASS, + PVOID, SIZE_T, PSIZE_T)); +extern PFNNTQUERYVIRTUALMEMORY g_pfnNtQueryVirtualMemory; +# endif + +# endif /* SUPHNTVI_NO_NT_STUFF */ + +/** Creates a combined NT version number for simple comparisons. */ +#define SUP_MAKE_NT_VER_COMBINED(a_uMajor, a_uMinor, a_uBuild, a_uSpMajor, a_uSpMinor) \ + ( ((uint32_t)((a_uMajor) & UINT32_C(0xf)) << 28) \ + | ((uint32_t)((a_uMinor) & UINT32_C(0xf)) << 24) \ + | ((uint32_t)((a_uBuild) & UINT32_C(0xffff)) << 8) \ + | ((uint32_t)((a_uSpMajor) & UINT32_C(0xf)) << 4) \ + | (uint32_t)((a_uSpMinor) & UINT32_C(0xf)) ) +/** Simple version of SUP_MAKE_NT_VER_COMBINED. */ +#define SUP_MAKE_NT_VER_SIMPLE(a_uMajor, a_uMinor) SUP_MAKE_NT_VER_COMBINED(a_uMajor, a_uMinor, 0, 0, 0) +extern uint32_t g_uNtVerCombined; + +/** @name NT version constants for less-than checks. + * @{ */ +/** Combined NT version number for XP. */ +#define SUP_NT_VER_XP SUP_MAKE_NT_VER_SIMPLE(5,1) +/** Combined NT version number for Windows server 2003 & XP64. */ +#define SUP_NT_VER_W2K3 SUP_MAKE_NT_VER_SIMPLE(5,2) +/** Combined NT version number for Vista. */ +#define SUP_NT_VER_VISTA SUP_MAKE_NT_VER_SIMPLE(6,0) +/** Combined NT version number for Vista with SP1. */ +#define SUP_NT_VER_VISTA_SP1 SUP_MAKE_NT_VER_COMBINED(6,0,6001,1,0) +/** Combined NT version number for Windows 7. */ +#define SUP_NT_VER_W70 SUP_MAKE_NT_VER_SIMPLE(6,1) +/** Combined NT version number for Windows 8.0. */ +#define SUP_NT_VER_W80 SUP_MAKE_NT_VER_SIMPLE(6,2) +/** Combined NT version number for Windows 8.1. */ +#define SUP_NT_VER_W81 SUP_MAKE_NT_VER_SIMPLE(6,3) +/** @} */ + +# endif + +# ifndef IN_SUP_HARDENED_R3 +# include <iprt/mem.h> +# include <iprt/string.h> + +# define suplibHardenedMemComp memcmp +# define suplibHardenedMemCopy memcpy +# define suplibHardenedMemSet memset +# define suplibHardenedStrCopy strcpy +# define suplibHardenedStrLen strlen +# define suplibHardenedStrCat strcat +# define suplibHardenedStrCmp strcmp +# define suplibHardenedStrNCmp strncmp +# else /* IN_SUP_HARDENED_R3 */ +# include <iprt/mem.h> +# if 0 +# define memcmp suplibHardenedMemComp +# define memcpy suplibHardenedMemCopy +# define memset suplibHardenedMemSet +# define strcpy suplibHardenedStrCopy +# define strlen suplibHardenedStrLen +# define strcat suplibHardenedStrCat +# define strcmp suplibHardenedStrCmp +# define strncmp suplibHardenedStrNCmp +# endif +# endif /* IN_SUP_HARDENED_R3 */ + +#endif /* SUP_CERTIFICATES_ONLY */ + +RT_C_DECLS_END + +#endif /* !VBOX_INCLUDED_SRC_Support_win_SUPHardenedVerify_win_h */ + diff --git a/src/VBox/HostDrivers/Support/win/SUPHardenedVerifyImage-win.cpp b/src/VBox/HostDrivers/Support/win/SUPHardenedVerifyImage-win.cpp new file mode 100644 index 00000000..747d975e --- /dev/null +++ b/src/VBox/HostDrivers/Support/win/SUPHardenedVerifyImage-win.cpp @@ -0,0 +1,3090 @@ +/* $Id: SUPHardenedVerifyImage-win.cpp $ */ +/** @file + * VirtualBox Support Library/Driver - Hardened Image Verification, Windows. + */ + +/* + * Copyright (C) 2006-2023 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 <https://www.gnu.org/licenses>. + * + * 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 <iprt/nt/nt.h> +# include <ntimage.h> +#else +# include <iprt/nt/nt-and-windows.h> +# include "Wintrust.h" +# include "Softpub.h" +# include "mscat.h" +# ifndef LOAD_LIBRARY_SEARCH_APPLICATION_DIR +# define LOAD_LIBRARY_SEARCH_SYSTEM32 0x800 +# endif +#endif + +#include <VBox/sup.h> +#include <VBox/err.h> +#include <iprt/ctype.h> +#include <iprt/ldr.h> +#include <iprt/log.h> +#include <iprt/path.h> +#include <iprt/string.h> +#include <iprt/utf16.h> +#include <iprt/crypto/pkcs7.h> +#include <iprt/crypto/store.h> + +#ifdef IN_RING0 +# include "SUPDrvInternal.h" +#else +# include "SUPLibInternal.h" +#endif +#include "win/SUPHardenedVerify-win.h" + + +/********************************************************************************************************************************* +* Defined Constants And Macros * +*********************************************************************************************************************************/ +/** The size of static hash (output) buffers. + * Avoids dynamic allocations and cleanups for of small buffers as well as extra + * calls for getting the appropriate buffer size. The largest digest in regular + * use by current windows version is SHA-512, we double this and hope it's + * enough a good while. */ +#define SUPHARDNTVI_MAX_CAT_HASH_SIZE 128 + + +#if defined(VBOX_PERMIT_EVEN_MORE) && !defined(VBOX_PERMIT_MORE) +# error "VBOX_PERMIT_EVEN_MORE without VBOX_PERMIT_MORE!" +#endif + + +/********************************************************************************************************************************* +* Structures and Typedefs * +*********************************************************************************************************************************/ + +#ifdef IN_RING3 +typedef DECLCALLBACKPTR_EX(LONG, WINAPI, PFNWINVERIFYTRUST,(HWND hwnd, GUID const *pgActionID, PVOID pWVTData)); +typedef DECLCALLBACKPTR_EX(BOOL, WINAPI, PFNCRYPTCATADMINACQUIRECONTEXT,(HCATADMIN *phCatAdmin, const GUID *pGuidSubsystem, + DWORD dwFlags)); +typedef DECLCALLBACKPTR_EX(BOOL, WINAPI, PFNCRYPTCATADMINACQUIRECONTEXT2,(HCATADMIN *phCatAdmin, const GUID *pGuidSubsystem, + PCWSTR pwszHashAlgorithm, + struct _CERT_STRONG_SIGN_PARA const *pStrongHashPolicy, + DWORD dwFlags)); +typedef DECLCALLBACKPTR_EX(BOOL, WINAPI, PFNCRYPTCATADMINCALCHASHFROMFILEHANDLE,(HANDLE hFile, DWORD *pcbHash, BYTE *pbHash, + DWORD dwFlags)); +typedef DECLCALLBACKPTR_EX(BOOL, WINAPI, PFNCRYPTCATADMINCALCHASHFROMFILEHANDLE2,(HCATADMIN hCatAdmin, HANDLE hFile, + DWORD *pcbHash, BYTE *pbHash, DWORD dwFlags)); +typedef DECLCALLBACKPTR_EX(HCATINFO, WINAPI, PFNCRYPTCATADMINENUMCATALOGFROMHASH,(HCATADMIN hCatAdmin, BYTE *pbHash, DWORD cbHash, + DWORD dwFlags, HCATINFO *phPrevCatInfo)); +typedef DECLCALLBACKPTR_EX(BOOL, WINAPI, PFNCRYPTCATADMINRELEASECATALOGCONTEXT,(HCATADMIN hCatAdmin, HCATINFO hCatInfo, + DWORD dwFlags)); +typedef DECLCALLBACKPTR_EX(BOOL, WINAPI, PFNCRYPTCATDADMINRELEASECONTEXT,(HCATADMIN hCatAdmin, DWORD dwFlags)); +typedef DECLCALLBACKPTR_EX(BOOL, WINAPI, PFNCRYPTCATCATALOGINFOFROMCONTEXT,(HCATINFO hCatInfo, CATALOG_INFO *psCatInfo, + DWORD dwFlags)); + +typedef DECLCALLBACKPTR_EX(HCERTSTORE, WINAPI, PFNCERTOPENSTORE,(PCSTR pszStoreProvider, DWORD dwEncodingType, + HCRYPTPROV_LEGACY hCryptProv, DWORD dwFlags, const void *pvParam)); +typedef DECLCALLBACKPTR_EX(BOOL, WINAPI, PFNCERTCLOSESTORE,(HCERTSTORE hCertStore, DWORD dwFlags)); +typedef DECLCALLBACKPTR_EX(PCCERT_CONTEXT, WINAPI, PFNCERTENUMCERTIFICATESINSTORE,(HCERTSTORE hCertStore, + PCCERT_CONTEXT pPrevCertContext)); + +typedef DECLCALLBACKPTR_EX(NTSTATUS, WINAPI, PFNBCRYPTOPENALGORTIHMPROVIDER,(BCRYPT_ALG_HANDLE *phAlgo, PCWSTR pwszAlgoId, + PCWSTR pwszImpl, DWORD dwFlags)); +#endif + + +/********************************************************************************************************************************* +* Global Variables * +*********************************************************************************************************************************/ +/** The build certificate. */ +static RTCRX509CERTIFICATE g_BuildX509Cert; + +/** Store for root software publisher certificates. */ +static RTCRSTORE g_hSpcRootStore = NIL_RTCRSTORE; +/** Store for root NT kernel certificates. */ +static RTCRSTORE g_hNtKernelRootStore = NIL_RTCRSTORE; + +/** Store containing SPC, NT kernel signing, and timestamp root certificates. */ +static RTCRSTORE g_hSpcAndNtKernelRootStore = NIL_RTCRSTORE; +/** Store for supplemental certificates for use with + * g_hSpcAndNtKernelRootStore. */ +static RTCRSTORE g_hSpcAndNtKernelSuppStore = NIL_RTCRSTORE; + +/** The full \\SystemRoot\\System32 path. */ +SUPSYSROOTDIRBUF g_System32NtPath; +/** The full \\SystemRoot\\WinSxS path. */ +SUPSYSROOTDIRBUF g_WinSxSNtPath; +#if defined(IN_RING3) && !defined(VBOX_PERMIT_EVEN_MORE) +/** The full 'Program Files' path. */ +SUPSYSROOTDIRBUF g_ProgramFilesNtPath; +# ifdef RT_ARCH_AMD64 +/** The full 'Program Files (x86)' path. */ +SUPSYSROOTDIRBUF g_ProgramFilesX86NtPath; +# endif +/** The full 'Common Files' path. */ +SUPSYSROOTDIRBUF g_CommonFilesNtPath; +# ifdef RT_ARCH_AMD64 +/** The full 'Common Files (x86)' path. */ +SUPSYSROOTDIRBUF g_CommonFilesX86NtPath; +# endif +#endif /* IN_RING3 && !VBOX_PERMIT_MORE*/ + +/** + * Blacklisted DLL names. + */ +const RTSTRTUPLE g_aSupNtViBlacklistedDlls[] = +{ + { RT_STR_TUPLE("SCROBJ.dll") }, + { NULL, 0 } /* terminator entry */ +}; + + +static union +{ + SID Sid; + uint8_t abPadding[SECURITY_MAX_SID_SIZE]; +} +/** The TrustedInstaller SID (Vista+). */ + g_TrustedInstallerSid, +/** Local system ID (S-1-5-21). */ + g_LocalSystemSid, +/** Builtin Administrators group alias (S-1-5-32-544). */ + g_AdminsGroupSid; + + +/** Set after we've retrived other SPC root certificates from the system. */ +static bool g_fHaveOtherRoots = false; + +#if defined(IN_RING3) && !defined(IN_SUP_HARDENED_R3) +/** Combined windows NT version number. See SUP_MAKE_NT_VER_COMBINED and + * SUP_MAKE_NT_VER_SIMPLE. */ +uint32_t g_uNtVerCombined; +#endif + +#ifdef IN_RING3 +/** Timestamp hack working around issues with old DLLs that we ship. + * See supHardenedWinVerifyImageByHandle() for details. */ +static uint64_t g_uBuildTimestampHack = 0; +#endif + +#ifdef IN_RING3 +/** Pointer to WinVerifyTrust. */ +PFNWINVERIFYTRUST g_pfnWinVerifyTrust; +/** Pointer to CryptCATAdminAcquireContext. */ +PFNCRYPTCATADMINACQUIRECONTEXT g_pfnCryptCATAdminAcquireContext; +/** Pointer to CryptCATAdminAcquireContext2 if available. */ +PFNCRYPTCATADMINACQUIRECONTEXT2 g_pfnCryptCATAdminAcquireContext2; +/** Pointer to CryptCATAdminCalcHashFromFileHandle. */ +PFNCRYPTCATADMINCALCHASHFROMFILEHANDLE g_pfnCryptCATAdminCalcHashFromFileHandle; +/** Pointer to CryptCATAdminCalcHashFromFileHandle2. */ +PFNCRYPTCATADMINCALCHASHFROMFILEHANDLE2 g_pfnCryptCATAdminCalcHashFromFileHandle2; +/** Pointer to CryptCATAdminEnumCatalogFromHash. */ +PFNCRYPTCATADMINENUMCATALOGFROMHASH g_pfnCryptCATAdminEnumCatalogFromHash; +/** Pointer to CryptCATAdminReleaseCatalogContext. */ +PFNCRYPTCATADMINRELEASECATALOGCONTEXT g_pfnCryptCATAdminReleaseCatalogContext; +/** Pointer to CryptCATAdminReleaseContext. */ +PFNCRYPTCATDADMINRELEASECONTEXT g_pfnCryptCATAdminReleaseContext; +/** Pointer to CryptCATCatalogInfoFromContext. */ +PFNCRYPTCATCATALOGINFOFROMCONTEXT g_pfnCryptCATCatalogInfoFromContext; + +/** Where we store the TLS entry for detecting WinVerifyTrustRecursion. */ +static uint32_t g_iTlsWinVerifyTrustRecursion = UINT32_MAX; +/** Fallback WinVerifyTrust recursion protection. */ +static uint32_t volatile g_idActiveThread = UINT32_MAX; + +#endif + + +/********************************************************************************************************************************* +* Internal Functions * +*********************************************************************************************************************************/ +#ifdef IN_RING3 +static int supR3HardNtViCallWinVerifyTrust(HANDLE hFile, PCRTUTF16 pwszName, uint32_t fFlags, PRTERRINFO pErrInfo, + PFNWINVERIFYTRUST pfnWinVerifyTrust, HRESULT *phrcWinVerifyTrust); +static int supR3HardNtViCallWinVerifyTrustCatFile(HANDLE hFile, PCRTUTF16 pwszName, uint32_t fFlags, PRTERRINFO pErrInfo, + PFNWINVERIFYTRUST pfnWinVerifyTrust); +#endif + + + + +/** @copydoc RTLDRREADER::pfnRead */ +static DECLCALLBACK(int) supHardNtViRdrRead(PRTLDRREADER pReader, void *pvBuf, size_t cb, RTFOFF off) +{ + PSUPHNTVIRDR pNtViRdr = (PSUPHNTVIRDR)pReader; + Assert(pNtViRdr->Core.uMagic == RTLDRREADER_MAGIC); + NTSTATUS rcNt; + + /* Check for type overflow (paranoia). */ + if ((ULONG)cb != cb) + return VERR_OUT_OF_RANGE; + +#ifdef IN_RING3 + /* Make sure the event semaphore is reset (normally we don't use one). */ + if (pNtViRdr->hEvent) + { + rcNt = NtClearEvent(pNtViRdr->hEvent); + if (!NT_SUCCESS(rcNt)) + return RTErrConvertFromNtStatus(rcNt); + } +#endif + + /* Perform the read. */ + LARGE_INTEGER offNt; + offNt.QuadPart = off; + + IO_STATUS_BLOCK Ios = RTNT_IO_STATUS_BLOCK_INITIALIZER; + rcNt = NtReadFile(pNtViRdr->hFile, + pNtViRdr->hEvent, + NULL /*ApcRoutine*/, + NULL /*ApcContext*/, + &Ios, + pvBuf, + (ULONG)cb, + &offNt, + NULL); + +#ifdef IN_RING0 + /* In ring-0 the handles shall be synchronized and not alertable. */ + AssertMsg(rcNt == STATUS_SUCCESS || !NT_SUCCESS(rcNt), ("%#x\n", rcNt)); +#else + /* In ring-3 we like our handles synchronized and non-alertable, but we + sometimes have to take what we can get. So, deal with pending I/O as + best we can. */ + if (rcNt == STATUS_PENDING) + rcNt = NtWaitForSingleObject(pNtViRdr->hEvent ? pNtViRdr->hEvent : pNtViRdr->hFile, FALSE /*Alertable*/, NULL); +#endif + if (NT_SUCCESS(rcNt)) + rcNt = Ios.Status; + if (NT_SUCCESS(rcNt)) + { + /* We require the caller to not read beyond the end of the file since + we don't have any way to communicate that we've read less that + requested. */ + if (Ios.Information == cb) + { + pNtViRdr->off = off + cb; /* (just for show) */ + return VINF_SUCCESS; + } +#ifdef IN_RING3 + supR3HardenedError(VERR_READ_ERROR, false, + "supHardNtViRdrRead: Only got %#zx bytes when requesting %#zx bytes at %#llx in '%s'.\n", + Ios.Information, off, cb, pNtViRdr->szFilename); +#endif + } + pNtViRdr->off = -1; + return VERR_READ_ERROR; +} + + +/** @copydoc RTLDRREADER::pfnTell */ +static DECLCALLBACK(RTFOFF) supHardNtViRdrTell(PRTLDRREADER pReader) +{ + PSUPHNTVIRDR pNtViRdr = (PSUPHNTVIRDR)pReader; + Assert(pNtViRdr->Core.uMagic == RTLDRREADER_MAGIC); + return pNtViRdr->off; +} + + +/** @copydoc RTLDRREADER::pfnSize */ +static DECLCALLBACK(uint64_t) supHardNtViRdrSize(PRTLDRREADER pReader) +{ + PSUPHNTVIRDR pNtViRdr = (PSUPHNTVIRDR)pReader; + Assert(pNtViRdr->Core.uMagic == RTLDRREADER_MAGIC); + return pNtViRdr->cbFile; +} + + +/** @copydoc RTLDRREADER::pfnLogName */ +static DECLCALLBACK(const char *) supHardNtViRdrLogName(PRTLDRREADER pReader) +{ + PSUPHNTVIRDR pNtViRdr = (PSUPHNTVIRDR)pReader; + return pNtViRdr->szFilename; +} + + +/** @copydoc RTLDRREADER::pfnMap */ +static DECLCALLBACK(int) supHardNtViRdrMap(PRTLDRREADER pReader, const void **ppvBits) +{ + RT_NOREF2(pReader, ppvBits); + return VERR_NOT_SUPPORTED; +} + + +/** @copydoc RTLDRREADER::pfnUnmap */ +static DECLCALLBACK(int) supHardNtViRdrUnmap(PRTLDRREADER pReader, const void *pvBits) +{ + RT_NOREF2(pReader, pvBits); + return VERR_NOT_SUPPORTED; +} + + +/** @copydoc RTLDRREADER::pfnDestroy */ +static DECLCALLBACK(int) supHardNtViRdrDestroy(PRTLDRREADER pReader) +{ + PSUPHNTVIRDR pNtViRdr = (PSUPHNTVIRDR)pReader; + Assert(pNtViRdr->Core.uMagic == RTLDRREADER_MAGIC); + + pNtViRdr->Core.uMagic = ~RTLDRREADER_MAGIC; + pNtViRdr->hFile = NULL; +#ifdef IN_RING3 + if (pNtViRdr->hEvent) + { + NtClose(pNtViRdr->hEvent); + pNtViRdr->hEvent = NULL; + } +#endif + RTMemFree(pNtViRdr); + return VINF_SUCCESS; +} + + +/** + * Creates a loader reader instance for the given NT file handle. + * + * @returns iprt status code. + * @param hFile Native NT file handle. + * @param pwszName Optional file name. + * @param fFlags Flags, SUPHNTVI_F_XXX. + * @param ppNtViRdr Where to store the reader instance on success. + */ +DECLHIDDEN(int) supHardNtViRdrCreate(HANDLE hFile, PCRTUTF16 pwszName, uint32_t fFlags, PSUPHNTVIRDR *ppNtViRdr) +{ + /* + * Try determine the size of the file. + */ + IO_STATUS_BLOCK Ios = RTNT_IO_STATUS_BLOCK_INITIALIZER; + FILE_STANDARD_INFORMATION StdInfo; + NTSTATUS rcNt = NtQueryInformationFile(hFile, &Ios, &StdInfo, sizeof(StdInfo), FileStandardInformation); + if (!NT_SUCCESS(rcNt) || !NT_SUCCESS(Ios.Status)) + return VERR_LDRVI_FILE_LENGTH_ERROR; + + /* + * Figure the file mode so we can see whether we'll be needing an event + * semaphore for waiting on reads. This may happen in very unlikely + * NtCreateSection scenarios. + */ +#if defined(IN_RING3) || defined(VBOX_STRICT) + Ios.Status = STATUS_UNSUCCESSFUL; + ULONG fMode; + rcNt = NtQueryInformationFile(hFile, &Ios, &fMode, sizeof(fMode), FileModeInformation); + if (!NT_SUCCESS(rcNt) || !NT_SUCCESS(Ios.Status)) + return VERR_SUP_VP_FILE_MODE_ERROR; +#endif + + HANDLE hEvent = NULL; +#ifdef IN_RING3 + if (!(fMode & (FILE_SYNCHRONOUS_IO_NONALERT | FILE_SYNCHRONOUS_IO_ALERT))) + { + rcNt = NtCreateEvent(&hEvent, EVENT_ALL_ACCESS, NULL, NotificationEvent, FALSE); + if (!NT_SUCCESS(rcNt)) + return VERR_SUP_VP_CREATE_READ_EVT_SEM_FAILED; + } +#else + Assert(fMode & FILE_SYNCHRONOUS_IO_NONALERT); +#endif + + /* + * Calc the file name length and allocate memory for the reader instance. + */ + size_t cchFilename = 0; + if (pwszName) + cchFilename = RTUtf16CalcUtf8Len(pwszName); + + int rc = VERR_NO_MEMORY; + PSUPHNTVIRDR pNtViRdr = (PSUPHNTVIRDR)RTMemAllocZ(sizeof(*pNtViRdr) + cchFilename); + if (!pNtViRdr) + { +#ifdef IN_RING3 + if (hEvent != NULL) + NtClose(hEvent); +#endif + return VERR_NO_MEMORY; + } + + /* + * Initialize the structure. + */ + if (cchFilename) + { + char *pszName = &pNtViRdr->szFilename[0]; + rc = RTUtf16ToUtf8Ex(pwszName, RTSTR_MAX, &pszName, cchFilename + 1, NULL); + AssertStmt(RT_SUCCESS(rc), pNtViRdr->szFilename[0] = '\0'); + } + else + pNtViRdr->szFilename[0] = '\0'; + + pNtViRdr->Core.uMagic = RTLDRREADER_MAGIC; + pNtViRdr->Core.pfnRead = supHardNtViRdrRead; + pNtViRdr->Core.pfnTell = supHardNtViRdrTell; + pNtViRdr->Core.pfnSize = supHardNtViRdrSize; + pNtViRdr->Core.pfnLogName = supHardNtViRdrLogName; + pNtViRdr->Core.pfnMap = supHardNtViRdrMap; + pNtViRdr->Core.pfnUnmap = supHardNtViRdrUnmap; + pNtViRdr->Core.pfnDestroy = supHardNtViRdrDestroy; + pNtViRdr->hFile = hFile; + pNtViRdr->hEvent = hEvent; + pNtViRdr->off = 0; + pNtViRdr->cbFile = (uint64_t)StdInfo.EndOfFile.QuadPart; + pNtViRdr->fFlags = fFlags; + *ppNtViRdr = pNtViRdr; + return VINF_SUCCESS; +} + + +/** + * Checks if the file is owned by TrustedInstaller (Vista+) or similar. + * + * @returns true if owned by TrustedInstaller of pre-Vista, false if not. + * + * @param hFile The handle to the file. + * @param pwszName The name of the file. + */ +static bool supHardNtViCheckIsOwnedByTrustedInstallerOrSimilar(HANDLE hFile, PCRTUTF16 pwszName) +{ + if (g_uNtVerCombined < SUP_NT_VER_VISTA) + return true; + + /* + * Get the ownership information. + */ + union + { + SECURITY_DESCRIPTOR_RELATIVE Rel; + SECURITY_DESCRIPTOR Abs; + uint8_t abView[256]; + } uBuf; + ULONG cbActual; + NTSTATUS rcNt = NtQuerySecurityObject(hFile, OWNER_SECURITY_INFORMATION, &uBuf.Abs, sizeof(uBuf), &cbActual); + if (!NT_SUCCESS(rcNt)) + { + SUP_DPRINTF(("NtQuerySecurityObject failed with rcNt=%#x on '%ls'\n", rcNt, pwszName)); + return false; + } + + /* + * Check the owner. + * + * Initially we wished to only allow TrustedInstaller. But a Windows CAPI + * plugin "Program Files\Tumbleweed\Desktop Validator\tmwdcapiclient.dll" + * turned up owned by the local system user, and we cannot operate without + * the plugin loaded once it's installed (WinVerityTrust fails). + * + * We'd like to avoid allowing Builtin\Administrators here since it's the + * default owner of anything an admin user creates (at least when elevated). + * Seems windows update or someone ends up installing or modifying system + * DLL ownership to this group, so for system32 and winsxs it's unavoidable. + * And, not surprise, a bunch of products, including AV, firewalls and similar + * ends up with their files installed with this group as owner. For instance + * if we wish to have NAT continue working, we need to allow this. + * + * Hopefully, we can limit the allowed files to these owners though, so + * we won't be subject to ordinary (non-admin, or not elevated) users + * downloading or be tricked into putting evil DLLs around the place... + */ + PSID pOwner = uBuf.Rel.Control & SE_SELF_RELATIVE ? &uBuf.abView[uBuf.Rel.Owner] : uBuf.Abs.Owner; + Assert((uintptr_t)pOwner - (uintptr_t)&uBuf < sizeof(uBuf) - sizeof(SID)); + if (RtlEqualSid(pOwner, &g_TrustedInstallerSid)) + return true; + if (RtlEqualSid(pOwner, &g_LocalSystemSid)) + return true; + if (RtlEqualSid(pOwner, &g_AdminsGroupSid)) + { + SUP_DPRINTF(("%ls: Owner is administrators group.\n", pwszName)); + return true; + } + + SUP_DPRINTF(("%ls: Owner is not trusted installer (%.*Rhxs)\n", + pwszName, ((uint8_t *)pOwner)[1] /*SubAuthorityCount*/ * sizeof(ULONG) + 8, pOwner)); + RT_NOREF1(pwszName); + return false; +} + + +/** + * Simple case insensitive UTF-16 / ASCII path compare. + * + * @returns true if equal, false if not. + * @param pawcLeft The UTF-16 path string, not necessarily null + * terminated. + * @param cwcLeft The number of chars in the left string, + * RTSTR_MAX if unknown but terminated. + * @param pszRight The ascii string. + */ +DECLHIDDEN(bool) supHardViUtf16PathIsEqualEx(PCRTUTF16 pawcLeft, size_t cwcLeft, const char *pszRight) +{ + for (;;) + { + RTUTF16 wc; + if (cwcLeft-- > 0) + wc =*pawcLeft++; + else + wc = 0; + uint8_t b = *pszRight++; + if (b != wc) + { + if (wc >= 0x80) + return false; + wc = RT_C_TO_LOWER(wc); + if (wc != b) + { + b = RT_C_TO_LOWER(b); + if (wc != b) + { + if (wc == '/') + wc = '\\'; + if (b == '/') + b = '\\'; + if (wc != b) + return false; + } + } + } + if (!b) + return true; + } +} + + +/** + * Simple case insensitive UTF-16 / ASCII path compare. + * + * @returns true if equal, false if not. + * @param pwszLeft The UTF-16 path string. + * @param pszRight The ascii string. + */ +static bool supHardViUtf16PathIsEqual(PCRTUTF16 pwszLeft, const char *pszRight) +{ + return supHardViUtf16PathIsEqualEx(pwszLeft, RTSTR_MAX, pszRight); +} + + +#if 0 /* unused */ +/** + * Simple case insensitive UTF-16 / ASCII ends-with path predicate. + * + * @returns true if equal, false if not. + * @param pwsz The UTF-16 path string. + * @param pszSuffix The ascii suffix string. + */ +static bool supHardViUtf16PathEndsWith(PCRTUTF16 pwsz, const char *pszSuffix) +{ + size_t cwc = RTUtf16Len(pwsz); + size_t cchSuffix = strlen(pszSuffix); + if (cwc >= cchSuffix) + return supHardViUtf16PathIsEqual(pwsz + cwc - cchSuffix, pszSuffix); + return false; +} +#endif + + +/** + * Simple case insensitive UTF-16 / ASCII starts-with path predicate. + * + * @returns true if starts with given string, false if not. + * @param pwszLeft The UTF-16 path string. + * @param pszRight The ascii prefix string. + */ +static bool supHardViUtf16PathStartsWithAscii(PCRTUTF16 pwszLeft, const char *pszRight) +{ + for (;;) + { + RTUTF16 wc = *pwszLeft++; + uint8_t b = *pszRight++; + if (b != wc) + { + if (!b) + return true; + if (wc >= 0x80 || wc == 0) + return false; + wc = RT_C_TO_LOWER(wc); + if (wc != b) + { + b = RT_C_TO_LOWER(b); + if (wc != b) + { + if (wc == '/') + wc = '\\'; + if (b == '/') + b = '\\'; + if (wc != b) + return false; + } + } + } + } +} + + +/** + * Simple case insensitive UNICODE_STRING starts-with path predicate. + * + * @returns true if starts with given string, false if not. + * @param pwszLeft The path to check. + * @param cwcLeft The length of @a pwszLeft + * @param pwszRight The starts-with path. + * @param cwcRight The length of @a pwszRight. + * @param fCheckSlash Check for a slash following the prefix. + */ +DECLHIDDEN(bool) supHardViUtf16PathStartsWithEx(PCRTUTF16 pwszLeft, uint32_t cwcLeft, + PCRTUTF16 pwszRight, uint32_t cwcRight, bool fCheckSlash) +{ + if (cwcLeft < cwcRight || !cwcRight || !pwszRight) + return false; + + /* See if we can get away with a case sensitive compare first. */ + if (memcmp(pwszLeft, pwszRight, cwcRight * sizeof(RTUTF16)) == 0) + pwszLeft += cwcRight; + else + { + /* No luck, do a slow case insensitive comapre. */ + uint32_t cLeft = cwcRight; + while (cLeft-- > 0) + { + RTUTF16 wcLeft = *pwszLeft++; + RTUTF16 wcRight = *pwszRight++; + if (wcLeft != wcRight) + { + wcLeft = wcLeft < 0x80 ? wcLeft == '/' ? '\\' : RT_C_TO_LOWER(wcLeft) : wcLeft; + wcRight = wcRight < 0x80 ? wcRight == '/' ? '\\' : RT_C_TO_LOWER(wcRight) : wcRight; + if (wcLeft != wcRight) + return false; + } + } + } + + /* Check for slash following the prefix, if request. */ + if ( !fCheckSlash + || *pwszLeft == '\\' + || *pwszLeft == '/') + return true; + return false; +} + + +/** + * Simple case insensitive UNICODE_STRING starts-with path predicate. + * + * @returns true if starts with given string, false if not. + * @param pUniStrLeft The path to check. + * @param pUniStrRight The starts-with path. + * @param fCheckSlash Check for a slash following the prefix. + */ +DECLHIDDEN(bool) supHardViUniStrPathStartsWithUniStr(UNICODE_STRING const *pUniStrLeft, + UNICODE_STRING const *pUniStrRight, bool fCheckSlash) +{ + return supHardViUtf16PathStartsWithEx(pUniStrLeft->Buffer, pUniStrLeft->Length / sizeof(WCHAR), + pUniStrRight->Buffer, pUniStrRight->Length / sizeof(WCHAR), fCheckSlash); +} + + +#ifndef IN_RING0 +/** + * Counts slashes in the given UTF-8 path string. + * + * @returns Number of slashes. + * @param pwsz The UTF-16 path string. + */ +static uint32_t supHardViUtf16PathCountSlashes(PCRTUTF16 pwsz) +{ + uint32_t cSlashes = 0; + RTUTF16 wc; + while ((wc = *pwsz++) != '\0') + if (wc == '/' || wc == '\\') + cSlashes++; + return cSlashes; +} +#endif + + +#ifdef VBOX_PERMIT_MORE +/** + * Checks if the path goes into %windir%\apppatch\. + * + * @returns true if apppatch, false if not. + * @param pwszPath The path to examine. + */ +DECLHIDDEN(bool) supHardViIsAppPatchDir(PCRTUTF16 pwszPath, uint32_t cwcName) +{ + uint32_t cwcWinDir = (g_System32NtPath.UniStr.Length - sizeof(L"System32")) / sizeof(WCHAR); + + if (cwcName <= cwcWinDir + sizeof("AppPatch")) + return false; + + if (memcmp(pwszPath, g_System32NtPath.UniStr.Buffer, cwcWinDir * sizeof(WCHAR))) + return false; + + if (!supHardViUtf16PathStartsWithAscii(&pwszPath[cwcWinDir], "\\AppPatch\\")) + return false; + + return g_uNtVerCombined >= SUP_NT_VER_VISTA; +} +#else +# error should not get here.. +#endif + + + +/** + * Checks if the unsigned DLL is fine or not. + * + * @returns VINF_LDRVI_NOT_SIGNED or @a rc. + * @param hLdrMod The loader module handle. + * @param pwszName The NT name of the DLL/EXE. + * @param fFlags Flags. + * @param hFile The file handle. + * @param rc The status code.. + */ +static int supHardNtViCheckIfNotSignedOk(RTLDRMOD hLdrMod, PCRTUTF16 pwszName, uint32_t fFlags, HANDLE hFile, int rc) +{ + RT_NOREF1(hLdrMod); + + if (fFlags & (SUPHNTVI_F_REQUIRE_BUILD_CERT | SUPHNTVI_F_REQUIRE_KERNEL_CODE_SIGNING)) + return rc; + + /* + * Version macros. + */ + uint32_t const uNtVer = g_uNtVerCombined; +#define IS_XP() ( uNtVer >= SUP_MAKE_NT_VER_SIMPLE(5, 1) && uNtVer < SUP_MAKE_NT_VER_SIMPLE(5, 2) ) +#define IS_W2K3() ( uNtVer >= SUP_MAKE_NT_VER_SIMPLE(5, 2) && uNtVer < SUP_MAKE_NT_VER_SIMPLE(5, 3) ) +#define IS_VISTA() ( uNtVer >= SUP_MAKE_NT_VER_SIMPLE(6, 0) && uNtVer < SUP_MAKE_NT_VER_SIMPLE(6, 1) ) +#define IS_W70() ( uNtVer >= SUP_MAKE_NT_VER_SIMPLE(6, 1) && uNtVer < SUP_MAKE_NT_VER_SIMPLE(6, 2) ) +#define IS_W80() ( uNtVer >= SUP_MAKE_NT_VER_SIMPLE(6, 2) && uNtVer < SUP_MAKE_NT_VER_SIMPLE(6, 3) ) +#define IS_W81() ( uNtVer >= SUP_MAKE_NT_VER_SIMPLE(6, 3) && uNtVer < SUP_MAKE_NT_VER_SIMPLE(6, 4) ) + + /* + * The System32 directory. + * + * System32 is full of unsigned DLLs shipped by microsoft, graphics + * hardware vendors, input device/method vendors and whatnot else that + * actually needs to be loaded into a process for it to work correctly. + * We have to ASSUME that anything our process attempts to load from + * System32 is trustworthy and that the Windows system with the help of + * anti-virus software make sure there is nothing evil lurking in System32 + * or being loaded from it. + * + * A small measure of protection is to list DLLs we know should be signed + * and decline loading unsigned versions of them, assuming they have been + * replaced by an adversary with evil intentions. + */ + PCRTUTF16 pwsz; + uint32_t cwcName = (uint32_t)RTUtf16Len(pwszName); + uint32_t cwcOther = g_System32NtPath.UniStr.Length / sizeof(WCHAR); + if (supHardViUtf16PathStartsWithEx(pwszName, cwcName, g_System32NtPath.UniStr.Buffer, cwcOther, true /*fCheckSlash*/)) + { + pwsz = pwszName + cwcOther + 1; + + /* Must be owned by trusted installer. (This test is superfuous, thus no relaxation here.) */ + if ( !(fFlags & SUPHNTVI_F_TRUSTED_INSTALLER_OWNER) + && !supHardNtViCheckIsOwnedByTrustedInstallerOrSimilar(hFile, pwszName)) + return rc; + + /* Core DLLs. */ + if (supHardViUtf16PathIsEqual(pwsz, "ntdll.dll")) + return uNtVer < SUP_NT_VER_VISTA ? VINF_LDRVI_NOT_SIGNED : rc; + if (supHardViUtf16PathIsEqual(pwsz, "kernel32.dll")) + return uNtVer < SUP_NT_VER_W81 ? VINF_LDRVI_NOT_SIGNED : rc; + if (supHardViUtf16PathIsEqual(pwsz, "kernelbase.dll")) + return IS_W80() || IS_W70() ? VINF_LDRVI_NOT_SIGNED : rc; + if (supHardViUtf16PathIsEqual(pwsz, "apisetschema.dll")) + return IS_W70() ? VINF_LDRVI_NOT_SIGNED : rc; + if (supHardViUtf16PathIsEqual(pwsz, "apphelp.dll")) + return VINF_LDRVI_NOT_SIGNED; /* So far, never signed... */ +#ifdef VBOX_PERMIT_VERIFIER_DLL + if (supHardViUtf16PathIsEqual(pwsz, "verifier.dll")) + return uNtVer < SUP_NT_VER_W81 ? VINF_LDRVI_NOT_SIGNED : rc; +#endif +#ifdef VBOX_PERMIT_MORE + if (uNtVer >= SUP_NT_VER_W70) /* hard limit: user32.dll is unwanted prior to w7. */ + { + if (supHardViUtf16PathIsEqual(pwsz, "sfc.dll")) + return uNtVer < SUP_MAKE_NT_VER_SIMPLE(6, 4) ? VINF_LDRVI_NOT_SIGNED : rc; + if (supHardViUtf16PathIsEqual(pwsz, "sfc_os.dll")) + return uNtVer < SUP_MAKE_NT_VER_SIMPLE(6, 4) ? VINF_LDRVI_NOT_SIGNED : rc; + if (supHardViUtf16PathIsEqual(pwsz, "user32.dll")) + return uNtVer < SUP_NT_VER_W81 ? VINF_LDRVI_NOT_SIGNED : rc; + } +#endif + +#ifndef IN_RING0 + /* Check that this DLL isn't supposed to be signed on this windows + version. If it should, it's likely to be a fake. */ + /** @todo list of signed dlls for various windows versions. */ + return VINF_LDRVI_NOT_SIGNED; +#else + return rc; +#endif /* IN_RING0 */ + } + + +#ifndef IN_RING0 + /* + * The WinSxS white list. + * + * Just like with System32 there are potentially a number of DLLs that + * could be required from WinSxS. + */ + cwcOther = g_WinSxSNtPath.UniStr.Length / sizeof(WCHAR); + if (supHardViUtf16PathStartsWithEx(pwszName, cwcName, g_WinSxSNtPath.UniStr.Buffer, cwcOther, true /*fCheckSlash*/)) + { + pwsz = pwszName + cwcOther + 1; + cwcName -= cwcOther + 1; + + /* The WinSxS layout means everything worth loading is exactly one level down. */ + uint32_t cSlashes = supHardViUtf16PathCountSlashes(pwsz); + if (cSlashes != 1) + return rc; + + /* Must be owned by trusted installer. */ + if ( !(fFlags & SUPHNTVI_F_TRUSTED_INSTALLER_OWNER) + && !supHardNtViCheckIsOwnedByTrustedInstallerOrSimilar(hFile, pwszName)) + return rc; + return VINF_LDRVI_NOT_SIGNED; + } +#endif /* !IN_RING0 */ + + +#ifdef VBOX_PERMIT_MORE + /* + * AppPatch whitelist. + */ + if (supHardViIsAppPatchDir(pwszName, cwcName)) + { + cwcOther = g_System32NtPath.UniStr.Length / sizeof(WCHAR); /* ASSUMES System32 is called System32. */ + pwsz = pwszName + cwcOther + 1; + + if ( !(fFlags & SUPHNTVI_F_TRUSTED_INSTALLER_OWNER) + && !supHardNtViCheckIsOwnedByTrustedInstallerOrSimilar(hFile, pwszName)) + return rc; + +# ifndef VBOX_PERMIT_EVEN_MORE + if (supHardViUtf16PathIsEqual(pwsz, "acres.dll")) + return VINF_LDRVI_NOT_SIGNED; + +# ifdef RT_ARCH_AMD64 + if (supHardViUtf16PathIsEqual(pwsz, "AppPatch64\\AcGenral.dll")) + return VINF_LDRVI_NOT_SIGNED; +# elif defined(RT_ARCH_X86) + if (supHardViUtf16PathIsEqual(pwsz, "AcGenral.dll")) + return VINF_LDRVI_NOT_SIGNED; +# endif +# endif /* !VBOX_PERMIT_EVEN_MORE */ + +# ifdef IN_RING0 + return rc; +# else + return VINF_LDRVI_NOT_SIGNED; +# endif + } +#endif /* VBOX_PERMIT_MORE */ + + +#ifndef IN_RING0 +# if defined(VBOX_PERMIT_MORE) && !defined(VBOX_PERMIT_EVEN_MORE) + /* + * Program files and common files. + * Permit anything that's signed and correctly installed. + */ + if ( supHardViUtf16PathStartsWithEx(pwszName, cwcName, + g_ProgramFilesNtPath.UniStr.Buffer, g_ProgramFilesNtPath.UniStr.Length, + true /*fCheckSlash*/) + || supHardViUtf16PathStartsWithEx(pwszName, cwcName, + g_CommonFilesNtPath.UniStr.Buffer, g_CommonFilesNtPath.UniStr.Length, + true /*fCheckSlash*/) +# ifdef RT_ARCH_AMD64 + || supHardViUtf16PathStartsWithEx(pwszName, cwcName, + g_ProgramFilesX86NtPath.UniStr.Buffer, g_ProgramFilesX86NtPath.UniStr.Length, + true /*fCheckSlash*/) + || supHardViUtf16PathStartsWithEx(pwszName, cwcName, + g_CommonFilesX86NtPath.UniStr.Buffer, g_CommonFilesX86NtPath.UniStr.Length, + true /*fCheckSlash*/) +# endif + ) + { + if ( !(fFlags & SUPHNTVI_F_TRUSTED_INSTALLER_OWNER) + && !supHardNtViCheckIsOwnedByTrustedInstallerOrSimilar(hFile, pwszName)) + return rc; + return VINF_LDRVI_NOT_SIGNED; + } + +# elif defined(VBOX_PERMIT_MORE) && defined(VBOX_PERMIT_EVEN_MORE) + /* + * Anything that's owned by the trusted installer. + */ + if ( (fFlags & SUPHNTVI_F_TRUSTED_INSTALLER_OWNER) + || supHardNtViCheckIsOwnedByTrustedInstallerOrSimilar(hFile, pwszName)) + return VINF_LDRVI_NOT_SIGNED; + +# endif +#endif /* !IN_RING0 */ + + /* + * Not permitted. + */ + return rc; +} + + +/** + * @callback_method_impl{FNRTDUMPPRINTFV, Formats into RTERRINFO. } + */ +static DECLCALLBACK(void) supHardNtViAsn1DumpToErrInfo(void *pvUser, const char *pszFormat, va_list va) +{ + PRTERRINFO pErrInfo = (PRTERRINFO)pvUser; + RTErrInfoAddV(pErrInfo, pErrInfo->rc, pszFormat, va); +} + + +/** + * Attempts to locate a root certificate in the specified store. + * + * @returns IPRT status code. + * @retval VINF_SUCCESS if found. + * @retval VWRN_NOT_FOUND if not found. + * + * @param hRootStore The root certificate store to search. + * @param pSubject The root certificate subject. + * @param pPublicKeyInfo The public key of the root certificate to find. + */ +static int supHardNtViCertVerifyFindRootCert(RTCRSTORE hRootStore, PCRTCRX509NAME pSubject, + PCRTCRX509SUBJECTPUBLICKEYINFO pPublicKeyInfo) +{ + RTCRSTORECERTSEARCH Search; + int rc = RTCrStoreCertFindBySubjectOrAltSubjectByRfc5280(hRootStore, pSubject, &Search); + AssertRCReturn(rc, rc); + + rc = VWRN_NOT_FOUND; + PCRTCRCERTCTX pCertCtx; + while ((pCertCtx = RTCrStoreCertSearchNext(hRootStore, &Search)) != NULL) + { + PCRTCRX509SUBJECTPUBLICKEYINFO pCertPubKeyInfo = NULL; + if (pCertCtx->pCert) + pCertPubKeyInfo = &pCertCtx->pCert->TbsCertificate.SubjectPublicKeyInfo; + else if (pCertCtx->pTaInfo) + pCertPubKeyInfo = &pCertCtx->pTaInfo->PubKey; + else + pCertPubKeyInfo = NULL; + if ( pCertPubKeyInfo + && RTCrX509SubjectPublicKeyInfo_Compare(pCertPubKeyInfo, pPublicKeyInfo) == 0) + { + RTCrCertCtxRelease(pCertCtx); + rc = VINF_SUCCESS; + break; + } + RTCrCertCtxRelease(pCertCtx); + } + + int rc2 = RTCrStoreCertSearchDestroy(hRootStore, &Search); + AssertRC(rc2); + return rc; +} + + +/** + * @callback_method_impl{FNRTCRPKCS7VERIFYCERTCALLBACK, + * Standard code signing. Use this for Microsoft SPC.} + */ +static DECLCALLBACK(int) supHardNtViCertVerifyCallback(PCRTCRX509CERTIFICATE pCert, RTCRX509CERTPATHS hCertPaths, + uint32_t fFlags, void *pvUser, PRTERRINFO pErrInfo) +{ + PSUPHNTVIRDR pNtViRdr = (PSUPHNTVIRDR)pvUser; + Assert(pNtViRdr->Core.uMagic == RTLDRREADER_MAGIC); + + /* + * If there is no certificate path build & validator associated with this + * callback, it must be because of the build certificate. We trust the + * build certificate without any second thoughts. + */ + if (RTCrX509Certificate_Compare(pCert, &g_BuildX509Cert) == 0) + { +#ifdef VBOX_STRICT + Assert(RTCrX509CertPathsGetPathCount(hCertPaths) == 1); + bool fTrusted = false; + uint32_t cNodes = UINT32_MAX; + int rcVerify = -1; + int rc = RTCrX509CertPathsQueryPathInfo(hCertPaths, 0, &fTrusted, &cNodes, NULL, NULL, NULL, NULL, &rcVerify); + AssertRC(rc); AssertRC(rcVerify); Assert(fTrusted); Assert(cNodes == 1); +#endif + return VINF_SUCCESS; + } + + /* + * Standard code signing capabilites required. + */ + int rc = RTCrPkcs7VerifyCertCallbackCodeSigning(pCert, hCertPaths, fFlags, NULL, pErrInfo); + if ( RT_SUCCESS(rc) + && (fFlags & RTCRPKCS7VCC_F_SIGNED_DATA)) + { + /* + * For kernel code signing there are two options for a valid certificate path: + * 1. Anchored by the microsoft kernel signing root certificate (g_hNtKernelRootStore). + * 2. Anchored by an SPC root and signing entity including a 1.3.6.1.4.1.311.10.3.5 (WHQL) + * or 1.3.6.1.4.1.311.10.3.5.1 (WHQL attestation) extended usage key. + */ + if (pNtViRdr->fFlags & SUPHNTVI_F_REQUIRE_KERNEL_CODE_SIGNING) + { + uint32_t cPaths = RTCrX509CertPathsGetPathCount(hCertPaths); + uint32_t cFound = 0; + uint32_t cValid = 0; + for (uint32_t iPath = 0; iPath < cPaths; iPath++) + { + bool fTrusted; + PCRTCRX509NAME pSubject; + PCRTCRX509SUBJECTPUBLICKEYINFO pPublicKeyInfo; + int rcVerify; + rc = RTCrX509CertPathsQueryPathInfo(hCertPaths, iPath, &fTrusted, NULL /*pcNodes*/, &pSubject, &pPublicKeyInfo, + NULL, NULL /*pCertCtx*/, &rcVerify); + AssertRCBreak(rc); + + if (RT_SUCCESS(rcVerify)) + { + Assert(fTrusted); + cValid++; + + /* + * 1. Search the kernel signing root store for a matching anchor. + */ + rc = supHardNtViCertVerifyFindRootCert(g_hNtKernelRootStore, pSubject, pPublicKeyInfo); + if (rc == VINF_SUCCESS) + cFound++; + /* + * 2. Check for WHQL EKU and make sure it has a SPC root. + */ + else if ( rc == VWRN_NOT_FOUND + && ( pCert->TbsCertificate.T3.fExtKeyUsage + & (RTCRX509CERT_EKU_F_MS_ATTEST_WHQL_CRYPTO | RTCRX509CERT_EKU_F_MS_WHQL_CRYPTO))) + { + rc = supHardNtViCertVerifyFindRootCert(g_hSpcRootStore, pSubject, pPublicKeyInfo); + if (rc == VINF_SUCCESS) + cFound++; + } + AssertRCBreak(rc); + } + } + if (RT_SUCCESS(rc) && cFound == 0) + rc = RTErrInfoSetF(pErrInfo, VERR_SUP_VP_NOT_VALID_KERNEL_CODE_SIGNATURE, + "Signature #%u/%u: Not valid kernel code signature.", + pNtViRdr->iCurSignature + 1, pNtViRdr->cTotalSignatures); + + + if (RT_SUCCESS(rc) && cValid < 2 && g_fHaveOtherRoots) + rc = RTErrInfoSetF(pErrInfo, VERR_SUP_VP_UNEXPECTED_VALID_PATH_COUNT, + "Signature #%u/%u: Expected at least %u valid paths, not %u.", + pNtViRdr->iCurSignature + 1, pNtViRdr->cTotalSignatures, 2, cValid); + if (rc == VWRN_NOT_FOUND) + rc = VINF_SUCCESS; + } + } + + /* + * More requirements? NT5 build lab? + */ + + return rc; +} + + +/** + * RTTimeNow equivaltent that handles ring-3 where we cannot use it. + * + * @returns pNow + * @param pNow Where to return the current time. + */ +static PRTTIMESPEC supHardNtTimeNow(PRTTIMESPEC pNow) +{ +#ifdef IN_RING3 + /* + * Just read system time. + */ + KUSER_SHARED_DATA volatile *pUserSharedData = (KUSER_SHARED_DATA volatile *)MM_SHARED_USER_DATA_VA; +# ifdef RT_ARCH_AMD64 + uint64_t uRet = *(uint64_t volatile *)&pUserSharedData->SystemTime; /* This is what KeQuerySystemTime does (missaligned). */ + return RTTimeSpecSetNtTime(pNow, uRet); +# else + + LARGE_INTEGER NtTime; + do + { + NtTime.HighPart = pUserSharedData->SystemTime.High1Time; + NtTime.LowPart = pUserSharedData->SystemTime.LowPart; + } while (pUserSharedData->SystemTime.High2Time != NtTime.HighPart); + return RTTimeSpecSetNtTime(pNow, NtTime.QuadPart); +# endif +#else /* IN_RING0 */ + return RTTimeNow(pNow); +#endif /* IN_RING0 */ +} + + +/** + * @callback_method_impl{FNRTLDRVALIDATESIGNEDDATA} + */ +static DECLCALLBACK(int) supHardNtViCallback(RTLDRMOD hLdrMod, PCRTLDRSIGNATUREINFO pInfo, PRTERRINFO pErrInfo, void *pvUser) +{ + RT_NOREF(hLdrMod); + + /* + * Check out the input. + */ + PSUPHNTVIRDR pNtViRdr = (PSUPHNTVIRDR)pvUser; + Assert(pNtViRdr->Core.uMagic == RTLDRREADER_MAGIC); + pNtViRdr->cTotalSignatures = pInfo->cSignatures; + pNtViRdr->iCurSignature = pInfo->iSignature; + + AssertReturn(pInfo->enmType == RTLDRSIGNATURETYPE_PKCS7_SIGNED_DATA, VERR_INTERNAL_ERROR_5); + AssertReturn(!pInfo->pvExternalData, VERR_INTERNAL_ERROR_5); + AssertReturn(pInfo->cbSignature == sizeof(RTCRPKCS7CONTENTINFO), VERR_INTERNAL_ERROR_5); + PCRTCRPKCS7CONTENTINFO pContentInfo = (PCRTCRPKCS7CONTENTINFO)pInfo->pvSignature; + AssertReturn(RTCrPkcs7ContentInfo_IsSignedData(pContentInfo), VERR_INTERNAL_ERROR_5); + AssertReturn(pContentInfo->u.pSignedData->SignerInfos.cItems == 1, VERR_INTERNAL_ERROR_5); + PCRTCRPKCS7SIGNERINFO pSignerInfo = pContentInfo->u.pSignedData->SignerInfos.papItems[0]; + + + /* + * If special certificate requirements, check them out before validating + * the signature. These only apply to the first signature (for now). + */ + if ( (pNtViRdr->fFlags & SUPHNTVI_F_REQUIRE_BUILD_CERT) + && pInfo->iSignature == 0) + { + if (!RTCrX509Certificate_MatchIssuerAndSerialNumber(&g_BuildX509Cert, + &pSignerInfo->IssuerAndSerialNumber.Name, + &pSignerInfo->IssuerAndSerialNumber.SerialNumber)) + return RTErrInfoSetF(pErrInfo, VERR_SUP_VP_NOT_SIGNED_WITH_BUILD_CERT, + "Signature #%u/%u: Not signed with the build certificate (serial %.*Rhxs, expected %.*Rhxs)", + pInfo->iSignature + 1, pInfo->cSignatures, + pSignerInfo->IssuerAndSerialNumber.SerialNumber.Asn1Core.cb, + pSignerInfo->IssuerAndSerialNumber.SerialNumber.Asn1Core.uData.pv, + g_BuildX509Cert.TbsCertificate.SerialNumber.Asn1Core.cb, + g_BuildX509Cert.TbsCertificate.SerialNumber.Asn1Core.uData.pv); + } + + /* + * We instruction the verifier to use the signing time counter signature + * when present, but provides the linker time then the current time as + * fallbacks should the timestamp be missing or unusable. + * + * Update: Save the first timestamp we validate with build cert and + * use this as a minimum timestamp for further build cert + * validations. This works around issues with old DLLs that + * we sign against with our certificate (crt, sdl, qt). + * + * Update: If the validation fails, retry with the current timestamp. This + * is a workaround for NTDLL.DLL in build 14971 having a weird + * timestamp: 0xDF1E957E (Sat Aug 14 14:05:18 2088). + */ + uint32_t fFlags = RTCRPKCS7VERIFY_SD_F_ALWAYS_USE_SIGNING_TIME_IF_PRESENT + | RTCRPKCS7VERIFY_SD_F_ALWAYS_USE_MS_TIMESTAMP_IF_PRESENT + | RTCRPKCS7VERIFY_SD_F_COUNTER_SIGNATURE_SIGNING_TIME_ONLY; + + /* In ring-0 we don't have all the necessary timestamp server root certificate + * info, so we have to allow using counter signatures unverified there. + * Ditto for the early period of ring-3 hardened stub execution. */ +#ifndef IN_RING0 + if (!g_fHaveOtherRoots) +#endif + fFlags |= RTCRPKCS7VERIFY_SD_F_USE_SIGNING_TIME_UNVERIFIED | RTCRPKCS7VERIFY_SD_F_USE_MS_TIMESTAMP_UNVERIFIED; + + /* Fallback timestamps to try: */ + struct { RTTIMESPEC TimeSpec; const char *pszDesc; } aTimes[2]; + unsigned cTimes = 0; + + /* 1. The linking timestamp: */ + uint64_t uTimestamp = 0; + int rc = RTLdrQueryProp(hLdrMod, RTLDRPROP_TIMESTAMP_SECONDS, &uTimestamp, sizeof(uTimestamp)); + if (RT_SUCCESS(rc)) + { +#ifdef IN_RING3 /* Hack alert! (see above) */ + if ( (pNtViRdr->fFlags & SUPHNTVI_F_REQUIRE_KERNEL_CODE_SIGNING) + && (pNtViRdr->fFlags & SUPHNTVI_F_REQUIRE_SIGNATURE_ENFORCEMENT) + && uTimestamp < g_uBuildTimestampHack) + uTimestamp = g_uBuildTimestampHack; +#endif + RTTimeSpecSetSeconds(&aTimes[0].TimeSpec, uTimestamp); + aTimes[0].pszDesc = "link"; + cTimes++; + } + else + SUP_DPRINTF(("RTLdrQueryProp/RTLDRPROP_TIMESTAMP_SECONDS failed on %s: %Rrc", pNtViRdr->szFilename, rc)); + + /* 2. Current time. */ + supHardNtTimeNow(&aTimes[cTimes].TimeSpec); + aTimes[cTimes].pszDesc = "now"; + cTimes++; + + /* Make the verfication attempts. */ + for (unsigned i = 0; ; i++) + { + Assert(i < cTimes); + rc = RTCrPkcs7VerifySignedData(pContentInfo, fFlags, g_hSpcAndNtKernelSuppStore, g_hSpcAndNtKernelRootStore, + &aTimes[i].TimeSpec, supHardNtViCertVerifyCallback, pNtViRdr, pErrInfo); + if (RT_SUCCESS(rc)) + { + if (rc != VINF_SUCCESS) + { + SUP_DPRINTF(("%s: Signature #%u/%u: info status: %d\n", pNtViRdr->szFilename, pInfo->iSignature + 1, pInfo->cSignatures, rc)); + if (pNtViRdr->rcLastSignatureFailure == VINF_SUCCESS) + pNtViRdr->rcLastSignatureFailure = rc; + } + pNtViRdr->cOkaySignatures++; + +#ifdef IN_RING3 /* Hack alert! (see above) */ + if ((pNtViRdr->fFlags & SUPHNTVI_F_REQUIRE_BUILD_CERT) && g_uBuildTimestampHack == 0 && cTimes > 1) + g_uBuildTimestampHack = uTimestamp; +#endif + return VINF_SUCCESS; + } + + if (rc == VERR_CR_X509_CPV_NOT_VALID_AT_TIME && i + 1 < cTimes) + SUP_DPRINTF(("%s: Signature #%u/%u: VERR_CR_X509_CPV_NOT_VALID_AT_TIME for %#RX64; retrying against current time: %#RX64.\n", + pNtViRdr->szFilename, pInfo->iSignature + 1, pInfo->cSignatures, + RTTimeSpecGetSeconds(&aTimes[0].TimeSpec), RTTimeSpecGetSeconds(&aTimes[1].TimeSpec))); + else + { + /* There are a couple of failures we can tollerate if there are more than + one signature and one of them works out fine. The RTLdrVerifySignature + caller will have to check the failure counts though to make sure + something succeeded. + + VERR_CR_PKCS7_KEY_USAGE_MISMATCH: Nvidia 391.35 nvldumpx.dll has an misconfigured + certificate "CN=NVIDIA Corporation PE Sign v2016" without valid Key Usage. It is + rooted by "CN=NVIDIA Subordinate CA 2016 v2,DC=nvidia,DC=com", so homebrewn. + Sysinternals' sigcheck util ignores it, while MS sigtool doesn't trust the root. + It's possible we're being too strict, but well, it's the only case so far, so no + need to relax the Key Usage restrictions just for a certificate w/o a trusted root. + + VERR_CR_X509_CPV_UNKNOWN_CRITICAL_EXTENSION: Intel 27.20.100.9126 igdumdim64.dll + has three signatures, the first is signed with a certificate (C=US,ST=CA, + L=Santa Clara,O=Intel Corporation,CN=IntelGraphicsPE2021) that has a critical + subject key identifier. This used to trip up the path validator. However, the + other two signatures are from microsoft and checks out fine. So, in future + situations like this it would be nice to simply continue with the next signature. + See bugref{10130} for details. + + VERR_SUP_VP_NOT_VALID_KERNEL_CODE_SIGNATURE: Is related to the above intel problem, + but this is what we get if suppressing the unknown critical subjectKeyIdentifier + in IPRT. We don't need all signatures to be valid kernel signatures, we should be + happy with just one and ignore any additional signatures as long as they don't look + like they've been compromised. Thus continue with this status too. */ + pNtViRdr->rcLastSignatureFailure = rc; + if ( rc == VERR_CR_X509_CPV_NOT_VALID_AT_TIME + || rc == VERR_CR_X509_CPV_NO_TRUSTED_PATHS + || rc == VERR_CR_PKCS7_KEY_USAGE_MISMATCH + || rc == VERR_CR_X509_CPV_UNKNOWN_CRITICAL_EXTENSION + || rc == VERR_SUP_VP_NOT_VALID_KERNEL_CODE_SIGNATURE) + { + SUP_DPRINTF(("%s: Signature #%u/%u: %s (%d) w/ timestamp=%#RX64/%s.\n", pNtViRdr->szFilename, pInfo->iSignature + 1, pInfo->cSignatures, + rc == VERR_CR_X509_CPV_NOT_VALID_AT_TIME ? "VERR_CR_X509_CPV_NOT_VALID_AT_TIME" + : rc == VERR_CR_X509_CPV_NO_TRUSTED_PATHS ? "VERR_CR_X509_CPV_NO_TRUSTED_PATHS" + : rc == VERR_CR_PKCS7_KEY_USAGE_MISMATCH ? "VERR_CR_PKCS7_KEY_USAGE_MISMATCH" + : rc == VERR_CR_X509_CPV_UNKNOWN_CRITICAL_EXTENSION ? "VERR_CR_X509_CPV_UNKNOWN_CRITICAL_EXTENSION" + : "VERR_SUP_VP_NOT_VALID_KERNEL_CODE_SIGNATURE", + rc, RTTimeSpecGetSeconds(&aTimes[i].TimeSpec), aTimes[i].pszDesc)); + + /* This leniency is not applicable to build certificate requirements (signature #1 only). */ + if ( !(pNtViRdr->fFlags & SUPHNTVI_F_REQUIRE_BUILD_CERT) + || pInfo->iSignature != 0) + { + pNtViRdr->cNokSignatures++; + rc = VINF_SUCCESS; + } + } + else + SUP_DPRINTF(("%s: Signature #%u/%u: %Rrc w/ timestamp=%#RX64/%s.\n", pNtViRdr->szFilename, pInfo->iSignature + 1, pInfo->cSignatures, + rc, RTTimeSpecGetSeconds(&aTimes[i].TimeSpec), aTimes[i].pszDesc)); + return rc; + } + } +} + + +/** + * Verifies the given loader image. + * + * @returns IPRT status code. + * @param hLdrMod File handle to the executable file. + * @param pwszName Full NT path to the DLL in question, used for + * dealing with unsigned system dlls as well as for + * error/logging. + * @param pNtViRdr The reader instance /w flags. + * @param fAvoidWinVerifyTrust Whether to avoid WinVerifyTrust because of + * deadlock or other loader related dangers. + * @param pfWinVerifyTrust Where to return whether WinVerifyTrust was used. + * @param pErrInfo Pointer to error info structure. Optional. + */ +DECLHIDDEN(int) supHardenedWinVerifyImageByLdrMod(RTLDRMOD hLdrMod, PCRTUTF16 pwszName, PSUPHNTVIRDR pNtViRdr, + bool fAvoidWinVerifyTrust, bool *pfWinVerifyTrust, PRTERRINFO pErrInfo) +{ + if (pfWinVerifyTrust) + *pfWinVerifyTrust = false; + +#ifdef IN_RING3 + /* Check that the caller has performed the necessary library initialization. */ + if (!RTCrX509Certificate_IsPresent(&g_BuildX509Cert)) + return RTErrInfoSet(pErrInfo, VERR_WRONG_ORDER, + "supHardenedWinVerifyImageByHandle: supHardenedWinInitImageVerifier was not called."); +#endif + + /* + * Check the trusted installer bit first, if requested as it's somewhat + * cheaper than the rest. + * + * We relax this for system32 and a little for WinSxS, like we used to, as + * there are apparently some systems out there where the user, admin, or + * someone has changed the ownership of core windows DLLs like user32.dll + * and comctl32.dll. Since we need user32.dll and will be checking it's + * digital signature, it's reasonably safe to let this thru. (The report + * was of SECURITY_BUILTIN_DOMAIN_RID + DOMAIN_ALIAS_RID_ADMINS + * owning user32.dll, see public ticket 13187, VBoxStartup.3.log.) + * + * We've also had problems with graphics driver components like ig75icd64.dll + * and atig6pxx.dll not being owned by TrustedInstaller, with the result + * that 3D got broken (mod by zero issue in test build 5). These were also + * SECURITY_BUILTIN_DOMAIN_RID + DOMAIN_ALIAS_RID_ADMINS. + * + * In one report by 'thor' the WinSxS resident comctl32.dll was owned by + * SECURITY_BUILTIN_DOMAIN_RID + DOMAIN_ALIAS_RID_ADMINS (with 4.3.16). + */ + /** @todo Since we're now allowing Builtin\\Administrators after all, perhaps we + * could drop these system32 + winsxs hacks?? */ + if ( (pNtViRdr->fFlags & SUPHNTVI_F_TRUSTED_INSTALLER_OWNER) + && !supHardNtViCheckIsOwnedByTrustedInstallerOrSimilar(pNtViRdr->hFile, pwszName)) + { + if (supHardViUtf16PathStartsWithEx(pwszName, (uint32_t)RTUtf16Len(pwszName), + g_System32NtPath.UniStr.Buffer, g_System32NtPath.UniStr.Length / sizeof(WCHAR), + true /*fCheckSlash*/)) + SUP_DPRINTF(("%ls: Relaxing the TrustedInstaller requirement for this DLL (it's in system32).\n", pwszName)); + else if (supHardViUtf16PathStartsWithEx(pwszName, (uint32_t)RTUtf16Len(pwszName), + g_WinSxSNtPath.UniStr.Buffer, g_WinSxSNtPath.UniStr.Length / sizeof(WCHAR), + true /*fCheckSlash*/)) + SUP_DPRINTF(("%ls: Relaxing the TrustedInstaller requirement for this DLL (it's in WinSxS).\n", pwszName)); + else + return RTErrInfoSetF(pErrInfo, VERR_SUP_VP_NOT_OWNED_BY_TRUSTED_INSTALLER, + "supHardenedWinVerifyImageByHandle: TrustedInstaller is not the owner of '%ls'.", pwszName); + } + + /* + * Verify it. + * + * The PKCS #7 SignedData signature is checked in the callback. Any + * signing certificate restrictions are also enforced there. + */ + pNtViRdr->cOkaySignatures = 0; + pNtViRdr->cNokSignatures = 0; + pNtViRdr->cTotalSignatures = 0; + pNtViRdr->rcLastSignatureFailure = VINF_SUCCESS; + int rc = RTLdrVerifySignature(hLdrMod, supHardNtViCallback, pNtViRdr, pErrInfo); + if (RT_SUCCESS(rc)) + { + Assert(pNtViRdr->cOkaySignatures + pNtViRdr->cNokSignatures == pNtViRdr->cTotalSignatures); + if ( !pNtViRdr->cOkaySignatures + || pNtViRdr->cOkaySignatures + pNtViRdr->cNokSignatures < pNtViRdr->cTotalSignatures /* paranoia */) + { + rc = pNtViRdr->rcLastSignatureFailure; + AssertStmt(RT_FAILURE_NP(rc), rc = VERR_INTERNAL_ERROR_3); + } + else if (rc == VINF_SUCCESS && RT_SUCCESS(pNtViRdr->rcLastSignatureFailure)) + rc = pNtViRdr->rcLastSignatureFailure; + } + + /* + * Microsoft doesn't sign a whole bunch of DLLs, so we have to + * ASSUME that a bunch of system DLLs are fine. + */ + if (rc == VERR_LDRVI_NOT_SIGNED) + rc = supHardNtViCheckIfNotSignedOk(hLdrMod, pwszName, pNtViRdr->fFlags, pNtViRdr->hFile, rc); + if (RT_FAILURE(rc)) + RTErrInfoAddF(pErrInfo, rc, ": %ls", pwszName); + + /* + * Check for the signature checking enforcement, if requested to do so. + */ + if (RT_SUCCESS(rc) && (pNtViRdr->fFlags & SUPHNTVI_F_REQUIRE_SIGNATURE_ENFORCEMENT)) + { + bool fEnforced = false; + int rc2 = RTLdrQueryProp(hLdrMod, RTLDRPROP_SIGNATURE_CHECKS_ENFORCED, &fEnforced, sizeof(fEnforced)); + if (RT_FAILURE(rc2)) + rc = RTErrInfoSetF(pErrInfo, rc2, "Querying RTLDRPROP_SIGNATURE_CHECKS_ENFORCED failed on %ls: %Rrc.", + pwszName, rc2); + else if (!fEnforced) + rc = RTErrInfoSetF(pErrInfo, VERR_SUP_VP_SIGNATURE_CHECKS_NOT_ENFORCED, + "The image '%ls' was not linked with /IntegrityCheck.", pwszName); + } + +#ifdef IN_RING3 + /* + * Pass it thru WinVerifyTrust when possible. + */ + if (!fAvoidWinVerifyTrust) + rc = supHardenedWinVerifyImageTrust(pNtViRdr->hFile, pwszName, pNtViRdr->fFlags, rc, pfWinVerifyTrust, pErrInfo); +#else + RT_NOREF1(fAvoidWinVerifyTrust); +#endif + + /* + * Check for blacklisted DLLs, both internal name and filename. + */ + if (RT_SUCCESS(rc)) + { + size_t const cwcName = RTUtf16Len(pwszName); + char szIntName[64]; + int rc2 = RTLdrQueryProp(hLdrMod, RTLDRPROP_INTERNAL_NAME, szIntName, sizeof(szIntName)); + if (RT_SUCCESS(rc2)) + { + size_t const cchIntName = strlen(szIntName); + for (unsigned i = 0; g_aSupNtViBlacklistedDlls[i].psz != NULL; i++) + if ( cchIntName == g_aSupNtViBlacklistedDlls[i].cch + && RTStrICmpAscii(szIntName, g_aSupNtViBlacklistedDlls[i].psz) == 0) + { + rc = RTErrInfoSetF(pErrInfo, VERR_SUP_VP_UNDESIRABLE_MODULE, + "The image '%ls' is listed as undesirable.", pwszName); + break; + } + } + if (RT_SUCCESS(rc)) + { + for (unsigned i = 0; g_aSupNtViBlacklistedDlls[i].psz != NULL; i++) + if (cwcName >= g_aSupNtViBlacklistedDlls[i].cch) + { + PCRTUTF16 pwszTmp = &pwszName[cwcName - g_aSupNtViBlacklistedDlls[i].cch]; + if ( ( cwcName == g_aSupNtViBlacklistedDlls[i].cch + || pwszTmp[-1] == '\\' + || pwszTmp[-1] == '/') + && RTUtf16ICmpAscii(pwszTmp, g_aSupNtViBlacklistedDlls[i].psz) == 0) + { + rc = RTErrInfoSetF(pErrInfo, VERR_SUP_VP_UNDESIRABLE_MODULE, + "The image '%ls' is listed as undesirable.", pwszName); + break; + } + } + } + } + +#ifdef IN_SUP_HARDENED_R3 + /* + * Hook for the LdrLoadDll code to schedule scanning of imports. + */ + if (RT_SUCCESS(rc)) + supR3HardenedWinVerifyCacheScheduleImports(hLdrMod, pwszName); +#endif + + return rc; +} + + +/** + * Verifies the given executable image. + * + * @returns IPRT status code. + * @param hFile File handle to the executable file. + * @param pwszName Full NT path to the DLL in question, used for + * dealing with unsigned system dlls as well as for + * error/logging. + * @param fFlags Flags, SUPHNTVI_F_XXX. + * @param fAvoidWinVerifyTrust Whether to avoid WinVerifyTrust because of + * deadlock or other loader related dangers. + * @param pfWinVerifyTrust Where to return whether WinVerifyTrust was used. + * @param pErrInfo Pointer to error info structure. Optional. + */ +DECLHIDDEN(int) supHardenedWinVerifyImageByHandle(HANDLE hFile, PCRTUTF16 pwszName, uint32_t fFlags, + bool fAvoidWinVerifyTrust, bool *pfWinVerifyTrust, PRTERRINFO pErrInfo) +{ + /* + * Create a reader instance. + */ + PSUPHNTVIRDR pNtViRdr; + int rc = supHardNtViRdrCreate(hFile, pwszName, fFlags, &pNtViRdr); + if (RT_SUCCESS(rc)) + { + /* + * Open the image. + */ + RTLDRMOD hLdrMod; + RTLDRARCH enmArch = fFlags & SUPHNTVI_F_RC_IMAGE ? RTLDRARCH_X86_32 : RTLDRARCH_HOST; + uint32_t fLdrFlags = RTLDR_O_FOR_VALIDATION | RTLDR_O_IGNORE_ARCH_IF_NO_CODE; + if (fFlags & SUPHNTVI_F_IGNORE_ARCHITECTURE) + fLdrFlags |= RTLDR_O_IGNORE_ARCH_IF_NO_CODE; + rc = RTLdrOpenWithReader(&pNtViRdr->Core, fLdrFlags, enmArch, &hLdrMod, pErrInfo); + if (RT_SUCCESS(rc)) + { + /* + * Verify it. + */ + rc = supHardenedWinVerifyImageByLdrMod(hLdrMod, pwszName, pNtViRdr, fAvoidWinVerifyTrust, pfWinVerifyTrust, pErrInfo); + int rc2 = RTLdrClose(hLdrMod); AssertRC(rc2); + } + else + supHardNtViRdrDestroy(&pNtViRdr->Core); + } + SUP_DPRINTF(("supHardenedWinVerifyImageByHandle: -> %d (%ls)%s\n", + rc, pwszName, pfWinVerifyTrust && *pfWinVerifyTrust ? " WinVerifyTrust" : "")); + return rc; +} + + +#ifdef IN_RING3 +/** + * supHardenedWinVerifyImageByHandle version without the name. + * + * The name is derived from the handle. + * + * @returns IPRT status code. + * @param hFile File handle to the executable file. + * @param fFlags Flags, SUPHNTVI_F_XXX. + * @param pErrInfo Pointer to error info structure. Optional. + */ +DECLHIDDEN(int) supHardenedWinVerifyImageByHandleNoName(HANDLE hFile, uint32_t fFlags, PRTERRINFO pErrInfo) +{ + /* + * Determine the NT name and call the verification function. + */ + union + { + UNICODE_STRING UniStr; + uint8_t abBuffer[(MAX_PATH + 8 + 1) * 2]; + } uBuf; + + ULONG cbIgn; + NTSTATUS rcNt = NtQueryObject(hFile, + ObjectNameInformation, + &uBuf, + sizeof(uBuf) - sizeof(WCHAR), + &cbIgn); + if (NT_SUCCESS(rcNt)) + uBuf.UniStr.Buffer[uBuf.UniStr.Length / sizeof(WCHAR)] = '\0'; + else + uBuf.UniStr.Buffer = (WCHAR *)L"TODO3"; + + return supHardenedWinVerifyImageByHandle(hFile, uBuf.UniStr.Buffer, fFlags, false /*fAvoidWinVerifyTrust*/, + NULL /*pfWinVerifyTrust*/, pErrInfo); +} +#endif /* IN_RING3 */ + + +/** + * Retrieves the full official path to the system root or one of it's sub + * directories. + * + * This code is also used by the support driver. + * + * @returns VBox status code. + * @param pvBuf The output buffer. This will contain a + * UNICODE_STRING followed (at the kernel's + * discretion) the string buffer. + * @param cbBuf The size of the buffer @a pvBuf points to. + * @param enmDir Which directory under the system root we're + * interested in. + * @param pErrInfo Pointer to error info structure. Optional. + */ +DECLHIDDEN(int) supHardNtGetSystemRootDir(void *pvBuf, uint32_t cbBuf, SUPHARDNTSYSROOTDIR enmDir, PRTERRINFO pErrInfo) +{ + HANDLE hFile = RTNT_INVALID_HANDLE_VALUE; + IO_STATUS_BLOCK Ios = RTNT_IO_STATUS_BLOCK_INITIALIZER; + + UNICODE_STRING NtName; + switch (enmDir) + { + case kSupHardNtSysRootDir_System32: + { + static const WCHAR s_wszNameSystem32[] = L"\\SystemRoot\\System32\\"; + NtName.Buffer = (PWSTR)s_wszNameSystem32; + NtName.Length = sizeof(s_wszNameSystem32) - sizeof(WCHAR); + NtName.MaximumLength = sizeof(s_wszNameSystem32); + break; + } + case kSupHardNtSysRootDir_WinSxS: + { + static const WCHAR s_wszNameWinSxS[] = L"\\SystemRoot\\WinSxS\\"; + NtName.Buffer = (PWSTR)s_wszNameWinSxS; + NtName.Length = sizeof(s_wszNameWinSxS) - sizeof(WCHAR); + NtName.MaximumLength = sizeof(s_wszNameWinSxS); + break; + } + default: + AssertFailed(); + return VERR_INVALID_PARAMETER; + } + + OBJECT_ATTRIBUTES ObjAttr; + InitializeObjectAttributes(&ObjAttr, &NtName, OBJ_CASE_INSENSITIVE, NULL /*hRootDir*/, NULL /*pSecDesc*/); + + NTSTATUS rcNt = NtCreateFile(&hFile, + FILE_READ_DATA | SYNCHRONIZE, + &ObjAttr, + &Ios, + NULL /* Allocation Size*/, + FILE_ATTRIBUTE_NORMAL, + FILE_SHARE_READ | FILE_SHARE_WRITE, + FILE_OPEN, + FILE_DIRECTORY_FILE | FILE_OPEN_FOR_BACKUP_INTENT | FILE_SYNCHRONOUS_IO_NONALERT, + NULL /*EaBuffer*/, + 0 /*EaLength*/); + if (NT_SUCCESS(rcNt)) + rcNt = Ios.Status; + if (NT_SUCCESS(rcNt)) + { + ULONG cbIgn; + rcNt = NtQueryObject(hFile, + ObjectNameInformation, + pvBuf, + cbBuf - sizeof(WCHAR), + &cbIgn); + NtClose(hFile); + if (NT_SUCCESS(rcNt)) + { + PUNICODE_STRING pUniStr = (PUNICODE_STRING)pvBuf; + if (pUniStr->Length > 0) + { + /* Make sure it's terminated so it can safely be printed.*/ + pUniStr->Buffer[pUniStr->Length / sizeof(WCHAR)] = '\0'; + return VINF_SUCCESS; + } + + return RTErrInfoSetF(pErrInfo, VERR_SUP_VP_SYSTEM32_PATH, + "NtQueryObject returned an empty path for '%ls'", NtName.Buffer); + } + return RTErrInfoSetF(pErrInfo, VERR_SUP_VP_SYSTEM32_PATH, "NtQueryObject failed on '%ls' dir: %#x", NtName.Buffer, rcNt); + } + return RTErrInfoSetF(pErrInfo, VERR_SUP_VP_SYSTEM32_PATH, "Failure to open '%ls': %#x", NtName.Buffer, rcNt); +} + + +/** + * Initialize one certificate entry. + * + * @returns VBox status code. + * @param pCert The X.509 certificate representation to init. + * @param pabCert The raw DER encoded certificate. + * @param cbCert The size of the raw certificate. + * @param pErrInfo Where to return extended error info. Optional. + * @param pszErrorTag Error tag. + */ +static int supHardNtViCertInit(PRTCRX509CERTIFICATE pCert, unsigned char const *pabCert, unsigned cbCert, + PRTERRINFO pErrInfo, const char *pszErrorTag) +{ + AssertReturn(cbCert > 16 && cbCert < _128K, + RTErrInfoSetF(pErrInfo, VERR_INTERNAL_ERROR_3, "%s: cbCert=%#x out of range", pszErrorTag, cbCert)); + AssertReturn(!RTCrX509Certificate_IsPresent(pCert), + RTErrInfoSetF(pErrInfo, VERR_WRONG_ORDER, "%s: Certificate already decoded?", pszErrorTag)); + + RTASN1CURSORPRIMARY PrimaryCursor; + RTAsn1CursorInitPrimary(&PrimaryCursor, pabCert, cbCert, pErrInfo, &g_RTAsn1DefaultAllocator, RTASN1CURSOR_FLAGS_DER, NULL); + int rc = RTCrX509Certificate_DecodeAsn1(&PrimaryCursor.Cursor, 0, pCert, pszErrorTag); + if (RT_SUCCESS(rc)) + rc = RTCrX509Certificate_CheckSanity(pCert, 0, pErrInfo, pszErrorTag); + return rc; +} + + +static int supHardNtViCertStoreAddArray(RTCRSTORE hStore, PCSUPTAENTRY paCerts, unsigned cCerts, PRTERRINFO pErrInfo) +{ + for (uint32_t i = 0; i < cCerts; i++) + { + int rc = RTCrStoreCertAddEncoded(hStore, RTCRCERTCTX_F_ENC_TAF_DER, paCerts[i].pch, paCerts[i].cb, pErrInfo); + if (RT_FAILURE(rc)) + return rc; + } + return VINF_SUCCESS; +} + + +/** + * Initialize a certificate table. + * + * @param phStore Where to return the store pointer. + * @param paCerts1 Pointer to the first certificate table. + * @param cCerts1 Entries in the first certificate table. + * @param paCerts2 Pointer to the second certificate table. + * @param cCerts2 Entries in the second certificate table. + * @param paCerts3 Pointer to the third certificate table. + * @param cCerts3 Entries in the third certificate table. + * @param pErrInfo Where to return extended error info. Optional. + * @param pszErrorTag Error tag. + */ +static int supHardNtViCertStoreInit(PRTCRSTORE phStore, + PCSUPTAENTRY paCerts1, unsigned cCerts1, + PCSUPTAENTRY paCerts2, unsigned cCerts2, + PCSUPTAENTRY paCerts3, unsigned cCerts3, + PRTERRINFO pErrInfo, const char *pszErrorTag) +{ + AssertReturn(*phStore == NIL_RTCRSTORE, VERR_WRONG_ORDER); + RT_NOREF1(pszErrorTag); + + int rc = RTCrStoreCreateInMem(phStore, cCerts1 + cCerts2); + if (RT_FAILURE(rc)) + return RTErrInfoSetF(pErrInfo, rc, "RTCrStoreCreateMemoryStore failed: %Rrc", rc); + + rc = supHardNtViCertStoreAddArray(*phStore, paCerts1, cCerts1, pErrInfo); + if (RT_SUCCESS(rc)) + rc = supHardNtViCertStoreAddArray(*phStore, paCerts2, cCerts2, pErrInfo); + if (RT_SUCCESS(rc)) + rc = supHardNtViCertStoreAddArray(*phStore, paCerts3, cCerts3, pErrInfo); + return rc; +} + + +#if defined(IN_RING3) && !defined(VBOX_PERMIT_EVEN_MORE) +/** + * Initializes the windows paths. + */ +static void supHardenedWinInitImageVerifierWinPaths(void) +{ + /* + * Windows paths that we're interested in. + */ + static const struct + { + SUPSYSROOTDIRBUF *pNtPath; + WCHAR const *pwszRegValue; + const char *pszLogName; + } s_aPaths[] = + { + { &g_ProgramFilesNtPath, L"ProgramFilesDir", "ProgDir" }, + { &g_CommonFilesNtPath, L"CommonFilesDir", "ComDir" }, +# ifdef RT_ARCH_AMD64 + { &g_ProgramFilesX86NtPath, L"ProgramFilesDir (x86)", "ProgDir32" }, + { &g_CommonFilesX86NtPath, L"CommonFilesDir (x86)", "ComDir32" }, +# endif + }; + + /* + * Open the registry key containing the paths. + */ + UNICODE_STRING NtName = RTNT_CONSTANT_UNISTR(L"\\Registry\\Machine\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion"); + OBJECT_ATTRIBUTES ObjAttr; + InitializeObjectAttributes(&ObjAttr, &NtName, OBJ_CASE_INSENSITIVE, NULL /*hRootDir*/, NULL /*pSecDesc*/); + HANDLE hKey; + NTSTATUS rcNt = NtOpenKey(&hKey, KEY_QUERY_VALUE, &ObjAttr); + if (NT_SUCCESS(rcNt)) + { + /* + * Loop over the paths and resolve their NT paths. + */ + for (uint32_t i = 0; i < RT_ELEMENTS(s_aPaths); i++) + { + /* + * Query the value first. + */ + UNICODE_STRING ValueName; + ValueName.Buffer = (WCHAR *)s_aPaths[i].pwszRegValue; + ValueName.Length = (USHORT)(RTUtf16Len(s_aPaths[i].pwszRegValue) * sizeof(WCHAR)); + ValueName.MaximumLength = ValueName.Length + sizeof(WCHAR); + + union + { + KEY_VALUE_PARTIAL_INFORMATION PartialInfo; + uint8_t abPadding[sizeof(KEY_VALUE_PARTIAL_INFORMATION) + sizeof(WCHAR) * 128]; + uint64_t uAlign; + } uBuf; + + ULONG cbActual = 0; + rcNt = NtQueryValueKey(hKey, &ValueName, KeyValuePartialInformation, &uBuf, sizeof(uBuf) - sizeof(WCHAR), &cbActual); + if (NT_SUCCESS(rcNt)) + { + /* + * Must be a simple string value, terminate it. + */ + if ( uBuf.PartialInfo.Type == REG_EXPAND_SZ + || uBuf.PartialInfo.Type == REG_SZ) + { + /* + * Expand any environment variable references before opening it. + * We use the result buffer as storage for the expaneded path, + * reserving space for the windows name space prefix. + */ + UNICODE_STRING Src; + Src.Buffer = (WCHAR *)uBuf.PartialInfo.Data; + Src.Length = uBuf.PartialInfo.DataLength; + if (Src.Length >= sizeof(WCHAR) && Src.Buffer[Src.Length / sizeof(WCHAR) - 1] == '\0') + Src.Length -= sizeof(WCHAR); + Src.MaximumLength = Src.Length + sizeof(WCHAR); + Src.Buffer[uBuf.PartialInfo.DataLength / sizeof(WCHAR)] = '\0'; + + s_aPaths[i].pNtPath->awcBuffer[0] = '\\'; + s_aPaths[i].pNtPath->awcBuffer[1] = '?'; + s_aPaths[i].pNtPath->awcBuffer[2] = '?'; + s_aPaths[i].pNtPath->awcBuffer[3] = '\\'; + UNICODE_STRING Dst; + Dst.Buffer = &s_aPaths[i].pNtPath->awcBuffer[4]; + Dst.MaximumLength = sizeof(s_aPaths[i].pNtPath->awcBuffer) - sizeof(WCHAR) * 5; + Dst.Length = Dst.MaximumLength; + + if (uBuf.PartialInfo.Type == REG_EXPAND_SZ) + rcNt = RtlExpandEnvironmentStrings_U(NULL, &Src, &Dst, NULL); + else + { + memcpy(Dst.Buffer, Src.Buffer, Src.Length); + Dst.Length = Src.Length; + } + if (NT_SUCCESS(rcNt)) + { + Dst.Buffer[Dst.Length / sizeof(WCHAR)] = '\0'; + + /* + * Include the \\??\\ prefix in the result and open the path. + */ + Dst.Buffer -= 4; + Dst.Length += 4 * sizeof(WCHAR); + Dst.MaximumLength += 4 * sizeof(WCHAR); + InitializeObjectAttributes(&ObjAttr, &Dst, OBJ_CASE_INSENSITIVE, NULL /*hRootDir*/, NULL /*pSecDesc*/); + HANDLE hFile = INVALID_HANDLE_VALUE; + IO_STATUS_BLOCK Ios = RTNT_IO_STATUS_BLOCK_INITIALIZER; + NTSTATUS rcNt = NtCreateFile(&hFile, + FILE_READ_DATA | SYNCHRONIZE, + &ObjAttr, + &Ios, + NULL /* Allocation Size*/, + FILE_ATTRIBUTE_NORMAL, + FILE_SHARE_READ | FILE_SHARE_WRITE, + FILE_OPEN, + FILE_DIRECTORY_FILE | FILE_OPEN_FOR_BACKUP_INTENT + | FILE_SYNCHRONOUS_IO_NONALERT, + NULL /*EaBuffer*/, + 0 /*EaLength*/); + if (NT_SUCCESS(rcNt)) + rcNt = Ios.Status; + if (NT_SUCCESS(rcNt)) + { + /* + * Query the real NT name. + */ + ULONG cbIgn; + rcNt = NtQueryObject(hFile, + ObjectNameInformation, + s_aPaths[i].pNtPath, + sizeof(*s_aPaths[i].pNtPath) - sizeof(WCHAR), + &cbIgn); + if (NT_SUCCESS(rcNt)) + { + if (s_aPaths[i].pNtPath->UniStr.Length > 0) + { + /* Make sure it's terminated.*/ + s_aPaths[i].pNtPath->UniStr.Buffer[s_aPaths[i].pNtPath->UniStr.Length / sizeof(WCHAR)] = '\0'; + SUP_DPRINTF(("%s:%*s %ls\n", s_aPaths[i].pszLogName, 9 - strlen(s_aPaths[i].pszLogName), "", + s_aPaths[i].pNtPath->UniStr.Buffer)); + } + else + { + SUP_DPRINTF(("%s: NtQueryObject returned empty string\n", s_aPaths[i].pszLogName)); + rcNt = STATUS_INVALID_PARAMETER; + } + } + else + SUP_DPRINTF(("%s: NtQueryObject failed: %#x\n", s_aPaths[i].pszLogName, rcNt)); + NtClose(hFile); + } + else + SUP_DPRINTF(("%s: NtCreateFile failed: %#x (%ls)\n", + s_aPaths[i].pszLogName, rcNt, Dst.Buffer)); + } + else + SUP_DPRINTF(("%s: RtlExpandEnvironmentStrings_U failed: %#x (%ls)\n", + s_aPaths[i].pszLogName, rcNt, Src.Buffer)); + } + else + { + SUP_DPRINTF(("%s: type mismatch: %#x\n", s_aPaths[i].pszLogName, uBuf.PartialInfo.Type)); + rcNt = STATUS_INVALID_PARAMETER; + } + } + else + SUP_DPRINTF(("%s: NtQueryValueKey failed: %#x\n", s_aPaths[i].pszLogName, rcNt)); + + /* Stub the entry on failure. */ + if (!NT_SUCCESS(rcNt)) + { + s_aPaths[i].pNtPath->UniStr.Length = 0; + s_aPaths[i].pNtPath->UniStr.Buffer = NULL; + } + } + NtClose(hKey); + } + else + { + SUP_DPRINTF(("NtOpenKey(%ls) failed: %#x\n", NtName.Buffer, rcNt)); + + /* Stub all the entries on failure. */ + for (uint32_t i = 0; i < RT_ELEMENTS(s_aPaths); i++) + { + s_aPaths[i].pNtPath->UniStr.Length = 0; + s_aPaths[i].pNtPath->UniStr.Buffer = NULL; + } + } +} +#endif /* IN_RING3 && !VBOX_PERMIT_EVEN_MORE */ + + +/** + * This initializes the certificates globals so we don't have to reparse them + * every time we need to verify an image. + * + * @returns IPRT status code. + * @param pErrInfo Where to return extended error info. Optional. + */ +DECLHIDDEN(int) supHardenedWinInitImageVerifier(PRTERRINFO pErrInfo) +{ + AssertReturn(!RTCrX509Certificate_IsPresent(&g_BuildX509Cert), VERR_WRONG_ORDER); + + /* + * Get the system root paths. + */ + int rc = supHardNtGetSystemRootDir(&g_System32NtPath, sizeof(g_System32NtPath), kSupHardNtSysRootDir_System32, pErrInfo); + if (RT_SUCCESS(rc)) + rc = supHardNtGetSystemRootDir(&g_WinSxSNtPath, sizeof(g_WinSxSNtPath), kSupHardNtSysRootDir_WinSxS, pErrInfo); + if (RT_SUCCESS(rc)) + { + SUP_DPRINTF(("System32: %ls\n", g_System32NtPath.UniStr.Buffer)); + SUP_DPRINTF(("WinSxS: %ls\n", g_WinSxSNtPath.UniStr.Buffer)); +#if defined(IN_RING3) && !defined(VBOX_PERMIT_EVEN_MORE) + supHardenedWinInitImageVerifierWinPaths(); +#endif + + /* + * Initialize it, leaving the cleanup to the termination call. + */ + rc = supHardNtViCertInit(&g_BuildX509Cert, g_abSUPBuildCert, g_cbSUPBuildCert, pErrInfo, "BuildCertificate"); + if (RT_SUCCESS(rc)) + rc = supHardNtViCertStoreInit(&g_hSpcRootStore, g_aSUPSpcRootTAs, g_cSUPSpcRootTAs, + NULL, 0, NULL, 0, pErrInfo, "SpcRoot"); + if (RT_SUCCESS(rc)) + rc = supHardNtViCertStoreInit(&g_hNtKernelRootStore, g_aSUPNtKernelRootTAs, g_cSUPNtKernelRootTAs, + NULL, 0, NULL, 0, pErrInfo, "NtKernelRoot"); + if (RT_SUCCESS(rc)) + rc = supHardNtViCertStoreInit(&g_hSpcAndNtKernelRootStore, + g_aSUPSpcRootTAs, g_cSUPSpcRootTAs, + g_aSUPNtKernelRootTAs, g_cSUPNtKernelRootTAs, + g_aSUPTimestampTAs, g_cSUPTimestampTAs, + pErrInfo, "SpcAndNtKernelRoot"); + if (RT_SUCCESS(rc)) + rc = supHardNtViCertStoreInit(&g_hSpcAndNtKernelSuppStore, + NULL, 0, NULL, 0, NULL, 0, + pErrInfo, "SpcAndNtKernelSupplemental"); + +#if 0 /* For the time being, always trust the build certificate. It bypasses the timestamp issues of CRT and SDL. */ + /* If the build certificate is a test singing certificate, it must be a + trusted root or we'll fail to validate anything. */ + if ( RT_SUCCESS(rc) + && RTCrX509Name_Compare(&g_BuildX509Cert.TbsCertificate.Subject, &g_BuildX509Cert.TbsCertificate.Issuer) == 0) +#else + if (RT_SUCCESS(rc)) +#endif + rc = RTCrStoreCertAddEncoded(g_hSpcAndNtKernelRootStore, RTCRCERTCTX_F_ENC_X509_DER, + g_abSUPBuildCert, g_cbSUPBuildCert, pErrInfo); + + if (RT_SUCCESS(rc)) + { + /* + * Finally initialize known SIDs that we use. + */ + SID_IDENTIFIER_AUTHORITY s_NtAuth = SECURITY_NT_AUTHORITY; + NTSTATUS rcNt = RtlInitializeSid(&g_TrustedInstallerSid, &s_NtAuth, SECURITY_SERVICE_ID_RID_COUNT); + if (NT_SUCCESS(rcNt)) + { + *RtlSubAuthoritySid(&g_TrustedInstallerSid, 0) = SECURITY_SERVICE_ID_BASE_RID; + *RtlSubAuthoritySid(&g_TrustedInstallerSid, 1) = 956008885; + *RtlSubAuthoritySid(&g_TrustedInstallerSid, 2) = 3418522649; + *RtlSubAuthoritySid(&g_TrustedInstallerSid, 3) = 1831038044; + *RtlSubAuthoritySid(&g_TrustedInstallerSid, 4) = 1853292631; + *RtlSubAuthoritySid(&g_TrustedInstallerSid, 5) = 2271478464; + + rcNt = RtlInitializeSid(&g_LocalSystemSid, &s_NtAuth, 1); + if (NT_SUCCESS(rcNt)) + { + *RtlSubAuthoritySid(&g_LocalSystemSid, 0) = SECURITY_LOCAL_SYSTEM_RID; + + rcNt = RtlInitializeSid(&g_AdminsGroupSid, &s_NtAuth, 2); + if (NT_SUCCESS(rcNt)) + { + *RtlSubAuthoritySid(&g_AdminsGroupSid, 0) = SECURITY_BUILTIN_DOMAIN_RID; + *RtlSubAuthoritySid(&g_AdminsGroupSid, 1) = DOMAIN_ALIAS_RID_ADMINS; + return VINF_SUCCESS; + } + } + } + rc = RTErrConvertFromNtStatus(rcNt); + } + supHardenedWinTermImageVerifier(); + } + return rc; +} + + +/** + * Releases resources allocated by supHardenedWinInitImageVerifier. + */ +DECLHIDDEN(void) supHardenedWinTermImageVerifier(void) +{ + if (RTCrX509Certificate_IsPresent(&g_BuildX509Cert)) + RTAsn1VtDelete(&g_BuildX509Cert.SeqCore.Asn1Core); + + RTCrStoreRelease(g_hSpcAndNtKernelSuppStore); + g_hSpcAndNtKernelSuppStore = NIL_RTCRSTORE; + RTCrStoreRelease(g_hSpcAndNtKernelRootStore); + g_hSpcAndNtKernelRootStore = NIL_RTCRSTORE; + + RTCrStoreRelease(g_hNtKernelRootStore); + g_hNtKernelRootStore = NIL_RTCRSTORE; + RTCrStoreRelease(g_hSpcRootStore); + g_hSpcRootStore = NIL_RTCRSTORE; +} + +#ifdef IN_RING3 + +/** + * This is a hardcoded list of certificates we thing we might need. + * + * @returns true if wanted, false if not. + * @param pCert The certificate. + */ +static bool supR3HardenedWinIsDesiredRootCA(PCRTCRX509CERTIFICATE pCert) +{ + char szSubject[512]; + szSubject[sizeof(szSubject) - 1] = '\0'; + RTCrX509Name_FormatAsString(&pCert->TbsCertificate.Subject, szSubject, sizeof(szSubject) - 1, NULL); + + /* + * Check that it's a plausible root certificate. + */ + if (!RTCrX509Certificate_IsSelfSigned(pCert)) + { + SUP_DPRINTF(("supR3HardenedWinIsDesiredRootCA: skipping - not-self-signed: %s\n", szSubject)); + return false; + } + + if (RTAsn1Integer_UnsignedCompareWithU32(&pCert->TbsCertificate.T0.Version, 3) > 0) + { + if ( !(pCert->TbsCertificate.T3.fExtKeyUsage & RTCRX509CERT_KEY_USAGE_F_KEY_CERT_SIGN) + && (pCert->TbsCertificate.T3.fFlags & RTCRX509TBSCERTIFICATE_F_PRESENT_KEY_USAGE) ) + { + SUP_DPRINTF(("supR3HardenedWinIsDesiredRootCA: skipping - non-cert-sign: %s\n", szSubject)); + return false; + } + if ( pCert->TbsCertificate.T3.pBasicConstraints + && !pCert->TbsCertificate.T3.pBasicConstraints->CA.fValue) + { + SUP_DPRINTF(("supR3HardenedWinIsDesiredRootCA: skipping - non-CA: %s\n", szSubject)); + return false; + } + } + if (pCert->TbsCertificate.SubjectPublicKeyInfo.SubjectPublicKey.cBits < 256) /* mostly for u64KeyId reading. */ + { + SUP_DPRINTF(("supR3HardenedWinIsDesiredRootCA: skipping - key too small: %u bits %s\n", + pCert->TbsCertificate.SubjectPublicKeyInfo.SubjectPublicKey.cBits, szSubject)); + return false; + } + uint64_t const u64KeyId = pCert->TbsCertificate.SubjectPublicKeyInfo.SubjectPublicKey.uBits.pu64[1]; + +# if 0 + /* + * Whitelist - Array of names and key clues of the certificates we want. + */ + static struct + { + uint64_t u64KeyId; + const char *pszName; + } const s_aWanted[] = + { + /* SPC */ + { UINT64_C(0xffffffffffffffff), "C=US, O=VeriSign, Inc., OU=Class 3 Public Primary Certification Authority" }, + { UINT64_C(0xffffffffffffffff), "L=Internet, O=VeriSign, Inc., OU=VeriSign Commercial Software Publishers CA" }, + { UINT64_C(0x491857ead79dde00), "C=US, O=The Go Daddy Group, Inc., OU=Go Daddy Class 2 Certification Authority" }, + + /* TS */ + { UINT64_C(0xffffffffffffffff), "O=Microsoft Trust Network, OU=Microsoft Corporation, OU=Microsoft Time Stamping Service Root, OU=Copyright (c) 1997 Microsoft Corp." }, + { UINT64_C(0xffffffffffffffff), "O=VeriSign Trust Network, OU=VeriSign, Inc., OU=VeriSign Time Stamping Service Root, OU=NO LIABILITY ACCEPTED, (c)97 VeriSign, Inc." }, + { UINT64_C(0xffffffffffffffff), "C=ZA, ST=Western Cape, L=Durbanville, O=Thawte, OU=Thawte Certification, CN=Thawte Timestamping CA" }, + + /* Additional Windows 8.1 list: */ + { UINT64_C(0x5ad46780fa5df300), "DC=com, DC=microsoft, CN=Microsoft Root Certificate Authority" }, + { UINT64_C(0x3be670c1bd02a900), "OU=Copyright (c) 1997 Microsoft Corp., OU=Microsoft Corporation, CN=Microsoft Root Authority" }, + { UINT64_C(0x4d3835aa4180b200), "C=US, ST=Washington, L=Redmond, O=Microsoft Corporation, CN=Microsoft Root Certificate Authority 2011" }, + { UINT64_C(0x646e3fe3ba08df00), "C=US, O=MSFT, CN=Microsoft Authenticode(tm) Root Authority" }, + { UINT64_C(0xece4e4289e08b900), "C=US, ST=Washington, L=Redmond, O=Microsoft Corporation, CN=Microsoft Root Certificate Authority 2010" }, + { UINT64_C(0x59faf1086271bf00), "C=US, ST=Arizona, L=Scottsdale, O=GoDaddy.com, Inc., CN=Go Daddy Root Certificate Authority - G2" }, + { UINT64_C(0x3d98ab22bb04a300), "C=IE, O=Baltimore, OU=CyberTrust, CN=Baltimore CyberTrust Root" }, + { UINT64_C(0x91e3728b8b40d000), "C=GB, ST=Greater Manchester, L=Salford, O=COMODO CA Limited, CN=COMODO Certification Authority" }, + { UINT64_C(0x61a3a33f81aace00), "C=US, ST=UT, L=Salt Lake City, O=The USERTRUST Network, OU=http://www.usertrust.com, CN=UTN-USERFirst-Object" }, + { UINT64_C(0x9e5bc2d78b6a3636), "C=ZA, ST=Western Cape, L=Cape Town, O=Thawte Consulting cc, OU=Certification Services Division, CN=Thawte Premium Server CA, Email=premium-server@thawte.com" }, + { UINT64_C(0xf4fd306318ccda00), "C=US, O=GeoTrust Inc., CN=GeoTrust Global CA" }, + { UINT64_C(0xa0ee62086758b15d), "C=US, O=Equifax, OU=Equifax Secure Certificate Authority" }, + { UINT64_C(0x8ff6fc03c1edbd00), "C=US, ST=Arizona, L=Scottsdale, O=Starfield Technologies, Inc., CN=Starfield Root Certificate Authority - G2" }, + { UINT64_C(0xa3ce8d99e60eda00), "C=BE, O=GlobalSign nv-sa, OU=Root CA, CN=GlobalSign Root CA" }, + { UINT64_C(0xa671e9fec832b700), "C=US, O=Starfield Technologies, Inc., OU=Starfield Class 2 Certification Authority" }, + { UINT64_C(0xa8de7211e13be200), "C=US, O=DigiCert Inc, OU=www.digicert.com, CN=DigiCert Global Root CA" }, + { UINT64_C(0x0ff3891b54348328), "C=US, O=Entrust.net, OU=www.entrust.net/CPS incorp. by ref. (limits liab.), OU=(c) 1999 Entrust.net Limited, CN=Entrust.netSecure Server Certification Authority" }, + { UINT64_C(0x7ae89c50f0b6a00f), "C=US, O=GTE Corporation, OU=GTE CyberTrust Solutions, Inc., CN=GTE CyberTrust Global Root" }, + { UINT64_C(0xd45980fbf0a0ac00), "C=US, O=thawte, Inc., OU=Certification Services Division, OU=(c) 2006 thawte, Inc. - For authorized use only, CN=thawte Primary Root CA" }, + { UINT64_C(0x9e5bc2d78b6a3636), "C=ZA, ST=Western Cape, L=Cape Town, O=Thawte Consulting cc, OU=Certification Services Division, CN=Thawte Premium Server CA, Email=premium-server@thawte.com" }, + { UINT64_C(0x7c4fd32ec1b1ce00), "C=PL, O=Unizeto Sp. z o.o., CN=Certum CA" }, + { UINT64_C(0xd4fbe673e5ccc600), "C=US, O=DigiCert Inc, OU=www.digicert.com, CN=DigiCert High Assurance EV Root CA" }, + { UINT64_C(0x16e64d2a56ccf200), "C=US, ST=Arizona, L=Scottsdale, O=Starfield Technologies, Inc., OU=http://certificates.starfieldtech.com/repository/, CN=Starfield Services Root Certificate Authority" }, + { UINT64_C(0x6e2ba21058eedf00), "C=US, ST=UT, L=Salt Lake City, O=The USERTRUST Network, OU=http://www.usertrust.com, CN=UTN - DATACorp SGC" }, + { UINT64_C(0xb28612a94b4dad00), "O=Entrust.net, OU=www.entrust.net/CPS_2048 incorp. by ref. (limits liab.), OU=(c) 1999 Entrust.net Limited, CN=Entrust.netCertification Authority (2048)" }, + { UINT64_C(0x357a29080824af00), "C=US, O=VeriSign, Inc., OU=VeriSign Trust Network, OU=(c) 2006 VeriSign, Inc. - For authorized use only, CN=VeriSign Class3 Public Primary Certification Authority - G5" }, + { UINT64_C(0x466cbc09db88c100), "C=IL, O=StartCom Ltd., OU=Secure Digital Certificate Signing, CN=StartCom Certification Authority" }, + { UINT64_C(0x9259c8abe5ca713a), "L=ValiCert Validation Network, O=ValiCert, Inc., OU=ValiCert Class 2 Policy Validation Authority, CN=http://www.valicert.com/, Email=info@valicert.com" }, + { UINT64_C(0x1f78fc529cbacb00), "C=US, O=VeriSign, Inc., OU=VeriSign Trust Network, OU=(c) 1999 VeriSign, Inc. - For authorized use only, CN=VeriSign Class3 Public Primary Certification Authority - G3" }, + { UINT64_C(0x8043e4ce150ead00), "C=US, O=DigiCert Inc, OU=www.digicert.com, CN=DigiCert Assured ID Root CA" }, + { UINT64_C(0x00f2e6331af7b700), "C=SE, O=AddTrust AB, OU=AddTrust External TTP Network, CN=AddTrust External CA Root" }, + }; + + + uint32_t i = RT_ELEMENTS(s_aWanted); + while (i-- > 0) + if ( s_aWanted[i].u64KeyId == u64KeyId + || s_aWanted[i].u64KeyId == UINT64_MAX) + if (RTCrX509Name_MatchWithString(&pCert->TbsCertificate.Subject, s_aWanted[i].pszName)) + { + SUP_DPRINTF(("supR3HardenedWinIsDesiredRootCA: Adding %#llx %s\n", u64KeyId, szSubject)); + return true; + } + + SUP_DPRINTF(("supR3HardenedWinIsDesiredRootCA: skipping %#llx %s\n", u64KeyId, szSubject)); + return false; +# else + /* + * Blacklist approach. + */ + static struct + { + uint64_t u64KeyId; + const char *pszName; + } const s_aUnwanted[] = + { + { UINT64_C(0xffffffffffffffff), "C=US, O=U.S. Robots and Mechanical Men, Inc., OU=V.I.K.I." }, /* dummy entry */ + }; + + uint32_t i = RT_ELEMENTS(s_aUnwanted); + while (i-- > 0) + if ( s_aUnwanted[i].u64KeyId == u64KeyId + || s_aUnwanted[i].u64KeyId == UINT64_MAX) + if (RTCrX509Name_MatchWithString(&pCert->TbsCertificate.Subject, s_aUnwanted[i].pszName)) + { + SUP_DPRINTF(("supR3HardenedWinIsDesiredRootCA: skipping - blacklisted: %#llx %s\n", u64KeyId, szSubject)); + return false; + } + + SUP_DPRINTF(("supR3HardenedWinIsDesiredRootCA: Adding %#llx %s\n", u64KeyId, szSubject)); + return true; +# endif +} + + +/** + * Loads a module in the system32 directory. + * + * @returns Module handle on success. Won't return on failure if fMandatory = true. + * @param pszName The name of the DLL to load. + * @param fMandatory Whether the library is mandatory. + */ +DECLHIDDEN(HMODULE) supR3HardenedWinLoadSystem32Dll(const char *pszName, bool fMandatory) +{ + WCHAR wszName[200+60]; + UINT cwcDir = GetSystemDirectoryW(wszName, RT_ELEMENTS(wszName) - 60); + wszName[cwcDir] = '\\'; + RTUtf16CopyAscii(&wszName[cwcDir + 1], RT_ELEMENTS(wszName) - cwcDir, pszName); + + DWORD fFlags = 0; + if (g_uNtVerCombined >= SUP_MAKE_NT_VER_SIMPLE(6, 0)) + fFlags = LOAD_LIBRARY_SEARCH_SYSTEM32; + HMODULE hMod = LoadLibraryExW(wszName, NULL, fFlags); + if ( hMod == NULL + && fFlags + && g_uNtVerCombined < SUP_MAKE_NT_VER_SIMPLE(6, 2) + && RtlGetLastWin32Error() == ERROR_INVALID_PARAMETER) + { + fFlags = 0; + hMod = LoadLibraryExW(wszName, NULL, fFlags); + } + if ( hMod == NULL + && fMandatory) + supR3HardenedFatal("Error loading '%s': %u [%ls]", pszName, RtlGetLastWin32Error(), wszName); + return hMod; +} + + +/** + * Called by supR3HardenedWinResolveVerifyTrustApiAndHookThreadCreation to + * import selected root CAs from the system certificate store. + * + * These certificates permits us to correctly validate third party DLLs. + */ +static void supR3HardenedWinRetrieveTrustedRootCAs(void) +{ + uint32_t cAdded = 0; + + /* + * Load crypt32.dll and resolve the APIs we need. + */ + HMODULE hCrypt32 = supR3HardenedWinLoadSystem32Dll("crypt32.dll", true /*fMandatory*/); + +#define RESOLVE_CRYPT32_API(a_Name, a_pfnType) \ + a_pfnType pfn##a_Name = (a_pfnType)GetProcAddress(hCrypt32, #a_Name); \ + if (pfn##a_Name == NULL) supR3HardenedFatal("Error locating '" #a_Name "' in 'crypt32.dll': %u", RtlGetLastWin32Error()) + RESOLVE_CRYPT32_API(CertOpenStore, PFNCERTOPENSTORE); + RESOLVE_CRYPT32_API(CertCloseStore, PFNCERTCLOSESTORE); + RESOLVE_CRYPT32_API(CertEnumCertificatesInStore, PFNCERTENUMCERTIFICATESINSTORE); +#undef RESOLVE_CRYPT32_API + + /* + * Open the root store and look for the certificates we wish to use. + */ + DWORD fOpenStore = CERT_STORE_OPEN_EXISTING_FLAG | CERT_STORE_READONLY_FLAG; + HCERTSTORE hStore = pfnCertOpenStore(CERT_STORE_PROV_SYSTEM_W, PKCS_7_ASN_ENCODING | X509_ASN_ENCODING, + NULL /* hCryptProv = default */, CERT_SYSTEM_STORE_LOCAL_MACHINE | fOpenStore, L"Root"); + if (!hStore) + hStore = pfnCertOpenStore(CERT_STORE_PROV_SYSTEM_W, PKCS_7_ASN_ENCODING | X509_ASN_ENCODING, + NULL /* hCryptProv = default */, CERT_SYSTEM_STORE_CURRENT_USER | fOpenStore, L"Root"); + if (hStore) + { + PCCERT_CONTEXT pCurCtx = NULL; + while ((pCurCtx = pfnCertEnumCertificatesInStore(hStore, pCurCtx)) != NULL) + { + if (pCurCtx->dwCertEncodingType & X509_ASN_ENCODING) + { + RTERRINFOSTATIC StaticErrInfo; + RTASN1CURSORPRIMARY PrimaryCursor; + RTAsn1CursorInitPrimary(&PrimaryCursor, pCurCtx->pbCertEncoded, pCurCtx->cbCertEncoded, + RTErrInfoInitStatic(&StaticErrInfo), + &g_RTAsn1DefaultAllocator, RTASN1CURSOR_FLAGS_DER, "CurCtx"); + RTCRX509CERTIFICATE MyCert; + int rc = RTCrX509Certificate_DecodeAsn1(&PrimaryCursor.Cursor, 0, &MyCert, "Cert"); + if (RT_SUCCESS(rc)) + { + if (supR3HardenedWinIsDesiredRootCA(&MyCert)) + { + rc = RTCrStoreCertAddEncoded(g_hSpcRootStore, RTCRCERTCTX_F_ENC_X509_DER, + pCurCtx->pbCertEncoded, pCurCtx->cbCertEncoded, NULL /*pErrInfo*/); + AssertRC(rc); + + rc = RTCrStoreCertAddEncoded(g_hSpcAndNtKernelRootStore, RTCRCERTCTX_F_ENC_X509_DER, + pCurCtx->pbCertEncoded, pCurCtx->cbCertEncoded, NULL /*pErrInfo*/); + AssertRC(rc); + cAdded++; + } + + RTCrX509Certificate_Delete(&MyCert); + } + /* XP root certificate "C&W HKT SecureNet CA SGC Root" has non-standard validity + timestamps, the UTC formatting isn't Zulu time but specifies timezone offsets. + Ignore these failures and certificates. */ + else if (rc != VERR_ASN1_INVALID_UTC_TIME_ENCODING) + AssertMsgFailed(("RTCrX509Certificate_DecodeAsn1 failed: rc=%#x: %s\n", rc, StaticErrInfo.szMsg)); + } + } + pfnCertCloseStore(hStore, CERT_CLOSE_STORE_CHECK_FLAG); + g_fHaveOtherRoots = true; + } + SUP_DPRINTF(("supR3HardenedWinRetrieveTrustedRootCAs: cAdded=%u\n", cAdded)); +} + + +/** + * Resolves the WinVerifyTrust API after the process has been verified and + * installs a thread creation hook. + * + * The WinVerifyTrust API is used in addition our own Authenticode verification + * code. If the image has the IMAGE_DLLCHARACTERISTICS_FORCE_INTEGRITY flag + * set, it will be checked again by the kernel. All our image has this flag set + * and we require all VBox extensions to have it set as well. In effect, the + * authenticode signature will be checked two or three times. + * + * @param pszProgName The program name. + */ +DECLHIDDEN(void) supR3HardenedWinResolveVerifyTrustApiAndHookThreadCreation(const char *pszProgName) +{ +# ifdef IN_SUP_HARDENED_R3 + /* + * Load our the support library DLL that does the thread hooking as the + * security API may trigger the creation of COM worker threads (or + * whatever they are). + * + * The thread creation hook makes the threads very slippery to debuggers by + * irreversably disabling most (if not all) debug events for them. + */ + char szPath[RTPATH_MAX]; + supR3HardenedPathAppSharedLibs(szPath, sizeof(szPath) - sizeof("/VBoxSupLib.DLL")); + suplibHardenedStrCat(szPath, "/VBoxSupLib.DLL"); + HMODULE hSupLibMod = (HMODULE)supR3HardenedWinLoadLibrary(szPath, true /*fSystem32Only*/, 0 /*fMainFlags*/); + if (hSupLibMod == NULL) + supR3HardenedFatal("Error loading '%s': %u", szPath, RtlGetLastWin32Error()); +# endif + + /* + * Allocate TLS entry for WinVerifyTrust recursion prevention. + */ + DWORD iTls = TlsAlloc(); + if (iTls != TLS_OUT_OF_INDEXES) + g_iTlsWinVerifyTrustRecursion = iTls; + else + supR3HardenedError(RtlGetLastWin32Error(), false /*fFatal*/, "TlsAlloc failed"); + + /* + * Resolve the imports we need. + */ + HMODULE hWintrust = supR3HardenedWinLoadSystem32Dll("Wintrust.dll", true /*fMandatory*/); +#define RESOLVE_CRYPT_API(a_Name, a_pfnType, a_uMinWinVer) \ + do { \ + g_pfn##a_Name = (a_pfnType)GetProcAddress(hWintrust, #a_Name); \ + if (g_pfn##a_Name == NULL && (a_uMinWinVer) < g_uNtVerCombined) \ + supR3HardenedFatal("Error locating '" #a_Name "' in 'Wintrust.dll': %u", RtlGetLastWin32Error()); \ + } while (0) + + PFNWINVERIFYTRUST pfnWinVerifyTrust = (PFNWINVERIFYTRUST)GetProcAddress(hWintrust, "WinVerifyTrust"); + if (!pfnWinVerifyTrust) + supR3HardenedFatal("Error locating 'WinVerifyTrust' in 'Wintrust.dll': %u", RtlGetLastWin32Error()); + + RESOLVE_CRYPT_API(CryptCATAdminAcquireContext, PFNCRYPTCATADMINACQUIRECONTEXT, 0); + RESOLVE_CRYPT_API(CryptCATAdminCalcHashFromFileHandle, PFNCRYPTCATADMINCALCHASHFROMFILEHANDLE, 0); + RESOLVE_CRYPT_API(CryptCATAdminEnumCatalogFromHash, PFNCRYPTCATADMINENUMCATALOGFROMHASH, 0); + RESOLVE_CRYPT_API(CryptCATAdminReleaseCatalogContext, PFNCRYPTCATADMINRELEASECATALOGCONTEXT, 0); + RESOLVE_CRYPT_API(CryptCATAdminReleaseContext, PFNCRYPTCATDADMINRELEASECONTEXT, 0); + RESOLVE_CRYPT_API(CryptCATCatalogInfoFromContext, PFNCRYPTCATCATALOGINFOFROMCONTEXT, 0); + + RESOLVE_CRYPT_API(CryptCATAdminAcquireContext2, PFNCRYPTCATADMINACQUIRECONTEXT2, SUP_NT_VER_W80); + RESOLVE_CRYPT_API(CryptCATAdminCalcHashFromFileHandle2, PFNCRYPTCATADMINCALCHASHFROMFILEHANDLE2, SUP_NT_VER_W80); + +# ifdef IN_SUP_HARDENED_R3 + /* + * Load bcrypt.dll and instantiate a few hashing and signing providers to + * make sure the providers are cached for later us. Avoid recursion issues. + */ + HMODULE hBCrypt = supR3HardenedWinLoadSystem32Dll("bcrypt.dll", false /*fMandatory*/); + if (hBCrypt) + { + PFNBCRYPTOPENALGORTIHMPROVIDER pfnOpenAlgoProvider; + pfnOpenAlgoProvider = (PFNBCRYPTOPENALGORTIHMPROVIDER)GetProcAddress(hBCrypt, "BCryptOpenAlgorithmProvider"); + if (pfnOpenAlgoProvider) + { + SUP_DPRINTF(("bcrypt.dll loaded at %p, BCryptOpenAlgorithmProvider at %p, preloading providers:\n", + hBCrypt, pfnOpenAlgoProvider)); +# define PRELOAD_ALGO_PROVIDER(a_Name) \ + do { \ + BCRYPT_ALG_HANDLE hAlgo = NULL; \ + NTSTATUS rcNt = pfnOpenAlgoProvider(&hAlgo, a_Name, NULL, 0); \ + SUP_DPRINTF(("%sBCryptOpenAlgorithmProvider(,'%ls',0,0) -> %#x (hAlgo=%p)\n", \ + NT_SUCCESS(rcNt) ? " " : "warning: ", a_Name, rcNt, hAlgo)); \ + } while (0) + PRELOAD_ALGO_PROVIDER(BCRYPT_MD2_ALGORITHM); + PRELOAD_ALGO_PROVIDER(BCRYPT_MD4_ALGORITHM); + PRELOAD_ALGO_PROVIDER(BCRYPT_MD5_ALGORITHM); + PRELOAD_ALGO_PROVIDER(BCRYPT_SHA1_ALGORITHM); + PRELOAD_ALGO_PROVIDER(BCRYPT_SHA256_ALGORITHM); + PRELOAD_ALGO_PROVIDER(BCRYPT_SHA512_ALGORITHM); + PRELOAD_ALGO_PROVIDER(BCRYPT_RSA_ALGORITHM); + PRELOAD_ALGO_PROVIDER(BCRYPT_DSA_ALGORITHM); +# undef PRELOAD_ALGO_PROVIDER + } + else + SUP_DPRINTF(("Warning! Failed to find BCryptOpenAlgorithmProvider in bcrypt.dll\n")); + } + else + SUP_DPRINTF(("Warning! Failed to load bcrypt.dll\n")); + + /* + * Call the verification API on ourselves and ntdll to make sure it works + * and loads more stuff it needs, preventing any recursive fun we'd run + * into after we set g_pfnWinVerifyTrust. + */ + RTERRINFOSTATIC ErrInfoStatic; + RTErrInfoInitStatic(&ErrInfoStatic); + int rc = supR3HardNtViCallWinVerifyTrust(NULL, g_SupLibHardenedExeNtPath.UniStr.Buffer, 0, + &ErrInfoStatic.Core, pfnWinVerifyTrust, NULL); + if (RT_FAILURE(rc)) + supR3HardenedFatalMsg(pszProgName, kSupInitOp_Integrity, rc, + "WinVerifyTrust failed on stub executable: %s", ErrInfoStatic.szMsg); +# else + RT_NOREF1(pszProgName); +# endif + + if (g_uNtVerCombined >= SUP_MAKE_NT_VER_SIMPLE(6, 0)) /* ntdll isn't signed on XP, assuming this is the case on W2K3 for now. */ + supR3HardNtViCallWinVerifyTrust(NULL, L"\\SystemRoot\\System32\\ntdll.dll", 0, NULL, pfnWinVerifyTrust, NULL); + supR3HardNtViCallWinVerifyTrustCatFile(NULL, L"\\SystemRoot\\System32\\ntdll.dll", 0, NULL, pfnWinVerifyTrust); + + g_pfnWinVerifyTrust = pfnWinVerifyTrust; + SUP_DPRINTF(("g_pfnWinVerifyTrust=%p\n", pfnWinVerifyTrust)); + +# ifdef IN_SUP_HARDENED_R3 + /* + * Load some problematic DLLs into the verifier cache to prevent + * recursion trouble. + */ + supR3HardenedWinVerifyCachePreload(L"\\SystemRoot\\System32\\crypt32.dll"); + supR3HardenedWinVerifyCachePreload(L"\\SystemRoot\\System32\\Wintrust.dll"); +# endif + + /* + * Now, get trusted root CAs so we can verify a broader scope of signatures. + */ + supR3HardenedWinRetrieveTrustedRootCAs(); +} + + +static int supR3HardNtViNtToWinPath(PCRTUTF16 pwszNtName, PCRTUTF16 *ppwszWinPath, + PRTUTF16 pwszWinPathBuf, size_t cwcWinPathBuf) +{ + static const RTUTF16 s_wszPrefix[] = L"\\\\.\\GLOBALROOT"; + + if (*pwszNtName != '\\' && *pwszNtName != '/') + return VERR_PATH_DOES_NOT_START_WITH_ROOT; + + size_t cwcNtName = RTUtf16Len(pwszNtName); + if (RT_ELEMENTS(s_wszPrefix) + cwcNtName > cwcWinPathBuf) + return VERR_FILENAME_TOO_LONG; + + memcpy(pwszWinPathBuf, s_wszPrefix, sizeof(s_wszPrefix)); + memcpy(&pwszWinPathBuf[sizeof(s_wszPrefix) / sizeof(RTUTF16) - 1], pwszNtName, (cwcNtName + 1) * sizeof(RTUTF16)); + *ppwszWinPath = pwszWinPathBuf; + return VINF_SUCCESS; +} + + +/** + * Calls WinVerifyTrust to verify an PE image. + * + * @returns VBox status code. + * @param hFile File handle to the executable file. + * @param pwszName Full NT path to the DLL in question, used for + * dealing with unsigned system dlls as well as for + * error/logging. + * @param fFlags Flags, SUPHNTVI_F_XXX. + * @param pErrInfo Pointer to error info structure. Optional. + * @param pfnWinVerifyTrust Pointer to the API. + * @param phrcWinVerifyTrust Where to WinVerifyTrust error status on failure, + * optional. + */ +static int supR3HardNtViCallWinVerifyTrust(HANDLE hFile, PCRTUTF16 pwszName, uint32_t fFlags, PRTERRINFO pErrInfo, + PFNWINVERIFYTRUST pfnWinVerifyTrust, HRESULT *phrcWinVerifyTrust) +{ + RT_NOREF1(fFlags); + if (phrcWinVerifyTrust) + *phrcWinVerifyTrust = S_OK; + + /* + * Convert the name into a Windows name. + */ + RTUTF16 wszWinPathBuf[MAX_PATH]; + PCRTUTF16 pwszWinPath; + int rc = supR3HardNtViNtToWinPath(pwszName, &pwszWinPath, wszWinPathBuf, RT_ELEMENTS(wszWinPathBuf)); + if (RT_FAILURE(rc)) + return RTErrInfoSetF(pErrInfo, rc, "Bad path passed to supR3HardNtViCallWinVerifyTrust: rc=%Rrc '%ls'", rc, pwszName); + + /* + * Construct input parameters and call the API. + */ + WINTRUST_FILE_INFO FileInfo; + RT_ZERO(FileInfo); + FileInfo.cbStruct = sizeof(FileInfo); + FileInfo.pcwszFilePath = pwszWinPath; + FileInfo.hFile = hFile; + + GUID PolicyActionGuid = WINTRUST_ACTION_GENERIC_VERIFY_V2; + + WINTRUST_DATA TrustData; + RT_ZERO(TrustData); + TrustData.cbStruct = sizeof(TrustData); + TrustData.fdwRevocationChecks = WTD_REVOKE_NONE; /* Keep simple for now. */ + TrustData.dwStateAction = WTD_STATEACTION_VERIFY; + TrustData.dwUIChoice = WTD_UI_NONE; + TrustData.dwProvFlags = 0; + if (g_uNtVerCombined >= SUP_MAKE_NT_VER_SIMPLE(6, 0)) + TrustData.dwProvFlags = WTD_CACHE_ONLY_URL_RETRIEVAL; + else + TrustData.dwProvFlags = WTD_REVOCATION_CHECK_NONE; + TrustData.dwUnionChoice = WTD_CHOICE_FILE; + TrustData.pFile = &FileInfo; + + HRESULT hrc = pfnWinVerifyTrust(NULL /*hwnd*/, &PolicyActionGuid, &TrustData); +# ifdef DEBUG_bird /* TEMP HACK */ + if (hrc == CERT_E_EXPIRED) + hrc = S_OK; +# endif + if (hrc == S_OK) + rc = VINF_SUCCESS; + else + { + /* + * Failed. Format a nice error message. + */ +# ifdef DEBUG_bird + if (hrc != CERT_E_CHAINING /* Un-updated vistas, XPs, ++ */) + __debugbreak(); +# endif + const char *pszErrConst = NULL; + switch (hrc) + { + case TRUST_E_SYSTEM_ERROR: pszErrConst = "TRUST_E_SYSTEM_ERROR"; break; + case TRUST_E_NO_SIGNER_CERT: pszErrConst = "TRUST_E_NO_SIGNER_CERT"; break; + case TRUST_E_COUNTER_SIGNER: pszErrConst = "TRUST_E_COUNTER_SIGNER"; break; + case TRUST_E_CERT_SIGNATURE: pszErrConst = "TRUST_E_CERT_SIGNATURE"; break; + case TRUST_E_TIME_STAMP: pszErrConst = "TRUST_E_TIME_STAMP"; break; + case TRUST_E_BAD_DIGEST: pszErrConst = "TRUST_E_BAD_DIGEST"; break; + case TRUST_E_BASIC_CONSTRAINTS: pszErrConst = "TRUST_E_BASIC_CONSTRAINTS"; break; + case TRUST_E_FINANCIAL_CRITERIA: pszErrConst = "TRUST_E_FINANCIAL_CRITERIA"; break; + case TRUST_E_PROVIDER_UNKNOWN: pszErrConst = "TRUST_E_PROVIDER_UNKNOWN"; break; + case TRUST_E_ACTION_UNKNOWN: pszErrConst = "TRUST_E_ACTION_UNKNOWN"; break; + case TRUST_E_SUBJECT_FORM_UNKNOWN: pszErrConst = "TRUST_E_SUBJECT_FORM_UNKNOWN"; break; + case TRUST_E_SUBJECT_NOT_TRUSTED: pszErrConst = "TRUST_E_SUBJECT_NOT_TRUSTED"; break; + case TRUST_E_NOSIGNATURE: pszErrConst = "TRUST_E_NOSIGNATURE"; break; + case TRUST_E_FAIL: pszErrConst = "TRUST_E_FAIL"; break; + case TRUST_E_EXPLICIT_DISTRUST: pszErrConst = "TRUST_E_EXPLICIT_DISTRUST"; break; + case CERT_E_EXPIRED: pszErrConst = "CERT_E_EXPIRED"; break; + case CERT_E_VALIDITYPERIODNESTING: pszErrConst = "CERT_E_VALIDITYPERIODNESTING"; break; + case CERT_E_ROLE: pszErrConst = "CERT_E_ROLE"; break; + case CERT_E_PATHLENCONST: pszErrConst = "CERT_E_PATHLENCONST"; break; + case CERT_E_CRITICAL: pszErrConst = "CERT_E_CRITICAL"; break; + case CERT_E_PURPOSE: pszErrConst = "CERT_E_PURPOSE"; break; + case CERT_E_ISSUERCHAINING: pszErrConst = "CERT_E_ISSUERCHAINING"; break; + case CERT_E_MALFORMED: pszErrConst = "CERT_E_MALFORMED"; break; + case CERT_E_UNTRUSTEDROOT: pszErrConst = "CERT_E_UNTRUSTEDROOT"; break; + case CERT_E_CHAINING: pszErrConst = "CERT_E_CHAINING"; break; + case CERT_E_REVOKED: pszErrConst = "CERT_E_REVOKED"; break; + case CERT_E_UNTRUSTEDTESTROOT: pszErrConst = "CERT_E_UNTRUSTEDTESTROOT"; break; + case CERT_E_REVOCATION_FAILURE: pszErrConst = "CERT_E_REVOCATION_FAILURE"; break; + case CERT_E_CN_NO_MATCH: pszErrConst = "CERT_E_CN_NO_MATCH"; break; + case CERT_E_WRONG_USAGE: pszErrConst = "CERT_E_WRONG_USAGE"; break; + case CERT_E_UNTRUSTEDCA: pszErrConst = "CERT_E_UNTRUSTEDCA"; break; + case CERT_E_INVALID_POLICY: pszErrConst = "CERT_E_INVALID_POLICY"; break; + case CERT_E_INVALID_NAME: pszErrConst = "CERT_E_INVALID_NAME"; break; + case CRYPT_E_FILE_ERROR: pszErrConst = "CRYPT_E_FILE_ERROR"; break; + case CRYPT_E_REVOKED: pszErrConst = "CRYPT_E_REVOKED"; break; + } + if (pszErrConst) + rc = RTErrInfoSetF(pErrInfo, VERR_LDRVI_UNSUPPORTED_ARCH, + "WinVerifyTrust failed with hrc=%s on '%ls'", pszErrConst, pwszName); + else + rc = RTErrInfoSetF(pErrInfo, VERR_LDRVI_UNSUPPORTED_ARCH, + "WinVerifyTrust failed with hrc=%Rhrc on '%ls'", hrc, pwszName); + SUP_DPRINTF(("supR3HardNtViCallWinVerifyTrust: WinVerifyTrust failed with %#x (%s) on '%ls'\n", + hrc, pszErrConst, pwszName)); + if (phrcWinVerifyTrust) + *phrcWinVerifyTrust = hrc; + } + + /* clean up state data. */ + TrustData.dwStateAction = WTD_STATEACTION_CLOSE; + FileInfo.hFile = NULL; + hrc = pfnWinVerifyTrust(NULL /*hwnd*/, &PolicyActionGuid, &TrustData); + + return rc; +} + + +/** + * Calls WinVerifyTrust to verify an PE image via catalog files. + * + * @returns VBox status code. + * @param hFile File handle to the executable file. + * @param pwszName Full NT path to the DLL in question, used for + * dealing with unsigned system dlls as well as for + * error/logging. + * @param fFlags Flags, SUPHNTVI_F_XXX. + * @param pErrInfo Pointer to error info structure. Optional. + * @param pfnWinVerifyTrust Pointer to the API. + */ +static int supR3HardNtViCallWinVerifyTrustCatFile(HANDLE hFile, PCRTUTF16 pwszName, uint32_t fFlags, PRTERRINFO pErrInfo, + PFNWINVERIFYTRUST pfnWinVerifyTrust) +{ + RT_NOREF1(fFlags); + SUP_DPRINTF(("supR3HardNtViCallWinVerifyTrustCatFile: hFile=%p pwszName=%ls\n", hFile, pwszName)); + + /* + * Convert the name into a Windows name. + */ + RTUTF16 wszWinPathBuf[MAX_PATH]; + PCRTUTF16 pwszWinPath; + int rc = supR3HardNtViNtToWinPath(pwszName, &pwszWinPath, wszWinPathBuf, RT_ELEMENTS(wszWinPathBuf)); + if (RT_FAILURE(rc)) + return RTErrInfoSetF(pErrInfo, rc, "Bad path passed to supR3HardNtViCallWinVerifyTrustCatFile: rc=%Rrc '%ls'", rc, pwszName); + + /* + * Open the file if we didn't get a handle. + */ + HANDLE hFileClose = NULL; + if (hFile == RTNT_INVALID_HANDLE_VALUE || hFile == NULL) + { + hFile = RTNT_INVALID_HANDLE_VALUE; + IO_STATUS_BLOCK Ios = RTNT_IO_STATUS_BLOCK_INITIALIZER; + + UNICODE_STRING NtName; + NtName.Buffer = (PWSTR)pwszName; + NtName.Length = (USHORT)(RTUtf16Len(pwszName) * sizeof(WCHAR)); + NtName.MaximumLength = NtName.Length + sizeof(WCHAR); + + OBJECT_ATTRIBUTES ObjAttr; + InitializeObjectAttributes(&ObjAttr, &NtName, OBJ_CASE_INSENSITIVE, NULL /*hRootDir*/, NULL /*pSecDesc*/); + + NTSTATUS rcNt = NtCreateFile(&hFile, + FILE_READ_DATA | READ_CONTROL | 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 RTErrInfoSetF(pErrInfo, RTErrConvertFromNtStatus(rcNt), + "NtCreateFile returned %#x opening '%ls'.", rcNt, pwszName); + hFileClose = hFile; + } + + /* + * On Windows 8.0 and later there are more than one digest choice. + */ + int fNoSignedCatalogFound = -1; + rc = VERR_LDRVI_NOT_SIGNED; + static struct + { + /** The digest algorithm name. */ + const WCHAR *pszAlgorithm; + /** Cached catalog admin handle. */ + HCATADMIN volatile hCachedCatAdmin; + } s_aHashes[] = + { + { NULL, NULL }, + { L"SHA256", NULL }, + }; + for (uint32_t i = 0; i < RT_ELEMENTS(s_aHashes); i++) + { + /* + * Another loop for dealing with different trust provider policies + * required for successfully validating different catalog signatures. + */ + bool fTryNextPolicy; + uint32_t iPolicy = 0; + static const GUID s_aPolicies[] = + { + DRIVER_ACTION_VERIFY, /* Works with microsoft bits. Most frequently used, thus first. */ + WINTRUST_ACTION_GENERIC_VERIFY_V2, /* Works with ATI and other SPC kernel-code signed stuff. */ + }; + do + { + /* + * Create a context. + */ + fTryNextPolicy = false; + bool fFreshContext = false; + BOOL fRc; + HCATADMIN hCatAdmin = ASMAtomicXchgPtr(&s_aHashes[i].hCachedCatAdmin, NULL); + if (hCatAdmin) + { + SUP_DPRINTF(("supR3HardNtViCallWinVerifyTrustCatFile: Cached context %p\n", hCatAdmin)); + fFreshContext = false; + fRc = TRUE; + } + else + { +l_fresh_context: + fFreshContext = true; + if (g_pfnCryptCATAdminAcquireContext2) + fRc = g_pfnCryptCATAdminAcquireContext2(&hCatAdmin, &s_aPolicies[iPolicy], s_aHashes[i].pszAlgorithm, + NULL /*pStrongHashPolicy*/, 0 /*dwFlags*/); + else + fRc = g_pfnCryptCATAdminAcquireContext(&hCatAdmin, &s_aPolicies[iPolicy], 0 /*dwFlags*/); + SUP_DPRINTF(("supR3HardNtViCallWinVerifyTrustCatFile: New context %p\n", hCatAdmin)); + } + if (fRc) + { + SUP_DPRINTF(("supR3HardNtViCallWinVerifyTrustCatFile: hCatAdmin=%p\n", hCatAdmin)); + + /* + * Hash the file. + */ + BYTE abHash[SUPHARDNTVI_MAX_CAT_HASH_SIZE]; + DWORD cbHash = sizeof(abHash); + if (g_pfnCryptCATAdminCalcHashFromFileHandle2) + fRc = g_pfnCryptCATAdminCalcHashFromFileHandle2(hCatAdmin, hFile, &cbHash, abHash, 0 /*dwFlags*/); + else + fRc = g_pfnCryptCATAdminCalcHashFromFileHandle(hFile, &cbHash, abHash, 0 /*dwFlags*/); + if (fRc) + { + /* Produce a string version of it that we can pass to WinVerifyTrust. */ + RTUTF16 wszDigest[SUPHARDNTVI_MAX_CAT_HASH_SIZE * 2 + 1]; + int rc2 = RTUtf16PrintHexBytes(wszDigest, RT_ELEMENTS(wszDigest), abHash, cbHash, RTSTRPRINTHEXBYTES_F_UPPER); + if (RT_SUCCESS(rc2)) + { + SUP_DPRINTF(("supR3HardNtViCallWinVerifyTrustCatFile: cbHash=%u wszDigest=%ls\n", cbHash, wszDigest)); + + /* + * Enumerate catalog information that matches the hash. + */ + uint32_t iCat = 0; + HCATINFO hCatInfoPrev = NULL; + do + { + /* Get the next match. */ + HCATINFO hCatInfo = g_pfnCryptCATAdminEnumCatalogFromHash(hCatAdmin, abHash, cbHash, 0, &hCatInfoPrev); + if (!hCatInfo) + { + if (!fFreshContext) + { + SUP_DPRINTF(("supR3HardNtViCallWinVerifyTrustCatFile: Retrying with fresh context (CryptCATAdminEnumCatalogFromHash -> %u; iCat=%#x)\n", RtlGetLastWin32Error(), iCat)); + if (hCatInfoPrev != NULL) + g_pfnCryptCATAdminReleaseCatalogContext(hCatAdmin, hCatInfoPrev, 0 /*dwFlags*/); + g_pfnCryptCATAdminReleaseContext(hCatAdmin, 0 /*dwFlags*/); + goto l_fresh_context; + } + ULONG ulErr = RtlGetLastWin32Error(); + fNoSignedCatalogFound = ulErr == ERROR_NOT_FOUND && fNoSignedCatalogFound != 0; + if (iCat == 0) + SUP_DPRINTF(("supR3HardNtViCallWinVerifyTrustCatFile: CryptCATAdminEnumCatalogFromHash failed ERROR_NOT_FOUND (%u)\n", ulErr)); + else if (iCat == 0) + SUP_DPRINTF(("supR3HardNtViCallWinVerifyTrustCatFile: CryptCATAdminEnumCatalogFromHash failed %u\n", ulErr)); + break; + } + fNoSignedCatalogFound = 0; + Assert(hCatInfoPrev == NULL); + hCatInfoPrev = hCatInfo; + + /* + * Call WinVerifyTrust. + */ + CATALOG_INFO CatInfo; + CatInfo.cbStruct = sizeof(CatInfo); + CatInfo.wszCatalogFile[0] = '\0'; + if (g_pfnCryptCATCatalogInfoFromContext(hCatInfo, &CatInfo, 0 /*dwFlags*/)) + { + WINTRUST_CATALOG_INFO WtCatInfo; + RT_ZERO(WtCatInfo); + WtCatInfo.cbStruct = sizeof(WtCatInfo); + WtCatInfo.dwCatalogVersion = 0; + WtCatInfo.pcwszCatalogFilePath = CatInfo.wszCatalogFile; + WtCatInfo.pcwszMemberTag = wszDigest; + WtCatInfo.pcwszMemberFilePath = pwszWinPath; + WtCatInfo.pbCalculatedFileHash = abHash; + WtCatInfo.cbCalculatedFileHash = cbHash; + WtCatInfo.pcCatalogContext = NULL; + + WINTRUST_DATA TrustData; + RT_ZERO(TrustData); + TrustData.cbStruct = sizeof(TrustData); + TrustData.fdwRevocationChecks = WTD_REVOKE_NONE; /* Keep simple for now. */ + TrustData.dwStateAction = WTD_STATEACTION_VERIFY; + TrustData.dwUIChoice = WTD_UI_NONE; + TrustData.dwProvFlags = 0; + if (g_uNtVerCombined >= SUP_MAKE_NT_VER_SIMPLE(6, 0)) + TrustData.dwProvFlags = WTD_CACHE_ONLY_URL_RETRIEVAL; + else + TrustData.dwProvFlags = WTD_REVOCATION_CHECK_NONE; + TrustData.dwUnionChoice = WTD_CHOICE_CATALOG; + TrustData.pCatalog = &WtCatInfo; + + HRESULT hrc = pfnWinVerifyTrust(NULL /*hwnd*/, &s_aPolicies[iPolicy], &TrustData); + SUP_DPRINTF(("supR3HardNtViCallWinVerifyTrustCatFile: WinVerifyTrust => %#x; cat='%ls'; file='%ls'\n", + hrc, CatInfo.wszCatalogFile, pwszName)); + + if (SUCCEEDED(hrc)) + rc = VINF_SUCCESS; + else if (hrc == TRUST_E_NOSIGNATURE) + { /* ignore because it's useless. */ } + else if (hrc == ERROR_INVALID_PARAMETER) + { /* This is returned if the given file isn't found in the catalog, it seems. */ } + else + { + rc = RTErrInfoSetF(pErrInfo, VERR_SUP_VP_WINTRUST_CAT_FAILURE, + "WinVerifyTrust failed with hrc=%#x on '%ls' and .cat-file='%ls'.", + hrc, pwszWinPath, CatInfo.wszCatalogFile); + fTryNextPolicy |= (hrc == CERT_E_UNTRUSTEDROOT); + } + + /* clean up state data. */ + TrustData.dwStateAction = WTD_STATEACTION_CLOSE; + hrc = pfnWinVerifyTrust(NULL /*hwnd*/, &s_aPolicies[iPolicy], &TrustData); + Assert(SUCCEEDED(hrc)); + } + else + { + rc = RTErrInfoSetF(pErrInfo, RTErrConvertFromWin32(RtlGetLastWin32Error()), + "CryptCATCatalogInfoFromContext failed: %d [file=%s]", + RtlGetLastWin32Error(), pwszName); + SUP_DPRINTF(("supR3HardNtViCallWinVerifyTrustCatFile: CryptCATCatalogInfoFromContext failed\n")); + } + iCat++; + } while (rc == VERR_LDRVI_NOT_SIGNED && iCat < 128); + + if (hCatInfoPrev != NULL) + if (!g_pfnCryptCATAdminReleaseCatalogContext(hCatAdmin, hCatInfoPrev, 0 /*dwFlags*/)) + AssertFailed(); + } + else + rc = RTErrInfoSetF(pErrInfo, rc2, "RTUtf16PrintHexBytes failed: %Rrc", rc); + } + else + rc = RTErrInfoSetF(pErrInfo, RTErrConvertFromWin32(RtlGetLastWin32Error()), + "CryptCATAdminCalcHashFromFileHandle[2] failed: %d [file=%s]", RtlGetLastWin32Error(), pwszName); + + if (!ASMAtomicCmpXchgPtr(&s_aHashes[i].hCachedCatAdmin, hCatAdmin, NULL)) + if (!g_pfnCryptCATAdminReleaseContext(hCatAdmin, 0 /*dwFlags*/)) + AssertFailed(); + } + else + rc = RTErrInfoSetF(pErrInfo, RTErrConvertFromWin32(RtlGetLastWin32Error()), + "CryptCATAdminAcquireContext[2] failed: %d [file=%s]", RtlGetLastWin32Error(), pwszName); + iPolicy++; + } while ( fTryNextPolicy + && iPolicy < RT_ELEMENTS(s_aPolicies)); + + /* + * Only repeat if we've got g_pfnCryptCATAdminAcquireContext2 and can specify the hash algorithm. + */ + if (!g_pfnCryptCATAdminAcquireContext2) + break; + if (rc != VERR_LDRVI_NOT_SIGNED) + break; + } + + if (hFileClose != NULL) + NtClose(hFileClose); + + /* + * DLLs that are likely candidates for local modifications. + */ + if (rc == VERR_LDRVI_NOT_SIGNED) + { + bool fCoreSystemDll = false; + PCRTUTF16 pwsz; + uint32_t cwcName = (uint32_t)RTUtf16Len(pwszName); + uint32_t cwcOther = g_System32NtPath.UniStr.Length / sizeof(WCHAR); + if (supHardViUtf16PathStartsWithEx(pwszName, cwcName, g_System32NtPath.UniStr.Buffer, cwcOther, true /*fCheckSlash*/)) + { + pwsz = pwszName + cwcOther + 1; + if ( supHardViUtf16PathIsEqual(pwsz, "uxtheme.dll") + || supHardViUtf16PathIsEqual(pwsz, "user32.dll") + || supHardViUtf16PathIsEqual(pwsz, "gdi32.dll") + || supHardViUtf16PathIsEqual(pwsz, "opengl32.dll") + || (fCoreSystemDll = supHardViUtf16PathIsEqual(pwsz, "KernelBase.dll")) + || (fCoreSystemDll = supHardViUtf16PathIsEqual(pwsz, "kernel32.dll")) + || (fCoreSystemDll = supHardViUtf16PathIsEqual(pwsz, "ntdll.dll")) + ) + { + if (RTErrInfoIsSet(pErrInfo)) + RTErrInfoAdd(pErrInfo, rc, "\n"); + RTErrInfoAddF(pErrInfo, rc, "'%ls' is most likely modified.", pwszName); + } + } + + /* Kludge for ancient windows versions we don't want to support but + users still wants to use. Keep things as safe as possible without + unnecessary effort. Problem is that 3rd party catalog files cannot + easily be found. Showstopper for ATI users. */ + if ( fNoSignedCatalogFound == 1 + && g_uNtVerCombined < SUP_NT_VER_VISTA + && !fCoreSystemDll) + { + rc = VINF_LDRVI_NOT_SIGNED; + } + } + + return rc; +} + + +/** + * Verifies the given image using WinVerifyTrust in some way. + * + * This is used by supHardenedWinVerifyImageByLdrMod as well as + * supR3HardenedScreenImage. + * + * @returns IPRT status code, modified @a rc. + * @param hFile Handle of the file to verify. + * @param pwszName Full NT path to the DLL in question, used for + * dealing with unsigned system dlls as well as for + * error/logging. + * @param fFlags SUPHNTVI_F_XXX. + * @param rc The current status code. + * @param pfWinVerifyTrust Where to return whether WinVerifyTrust was + * actually used. + * @param pErrInfo Pointer to error info structure. Optional. + */ +DECLHIDDEN(int) supHardenedWinVerifyImageTrust(HANDLE hFile, PCRTUTF16 pwszName, uint32_t fFlags, int rc, + bool *pfWinVerifyTrust, PRTERRINFO pErrInfo) +{ + if (pfWinVerifyTrust) + *pfWinVerifyTrust = false; + + /* + * Call the windows verify trust API if we've resolved it and aren't in + * some obvious recursion. + */ + if (g_pfnWinVerifyTrust != NULL) + { + uint32_t const idCurrentThread = RTNtCurrentThreadId(); + + /* Check if loader lock owner. */ + struct _RTL_CRITICAL_SECTION volatile *pLoaderLock = NtCurrentPeb()->LoaderLock; + bool fOwnsLoaderLock = pLoaderLock + && pLoaderLock->OwningThread == (HANDLE)(uintptr_t)idCurrentThread + && pLoaderLock->RecursionCount > 0; + if (!fOwnsLoaderLock) + { + /* Check for recursion. */ + bool fNoRecursion; + if (g_iTlsWinVerifyTrustRecursion != UINT32_MAX) + { + fNoRecursion = TlsGetValue(g_iTlsWinVerifyTrustRecursion) == 0; + if (fNoRecursion) + TlsSetValue(g_iTlsWinVerifyTrustRecursion, (void *)1); + } + else + fNoRecursion = ASMAtomicCmpXchgU32(&g_idActiveThread, idCurrentThread, UINT32_MAX); + + if (fNoRecursion && !fOwnsLoaderLock) + { + /* We can call WinVerifyTrust. */ + if (pfWinVerifyTrust) + *pfWinVerifyTrust = true; + + if (rc != VERR_LDRVI_NOT_SIGNED) + { + if (rc == VINF_LDRVI_NOT_SIGNED) + { + if (fFlags & SUPHNTVI_F_ALLOW_CAT_FILE_VERIFICATION) + { + int rc2 = supR3HardNtViCallWinVerifyTrustCatFile(hFile, pwszName, fFlags, pErrInfo, + g_pfnWinVerifyTrust); + SUP_DPRINTF(("supR3HardNtViCallWinVerifyTrustCatFile -> %d (org %d)\n", rc2, rc)); + rc = rc2; + } + else + { + AssertFailed(); + rc = VERR_LDRVI_NOT_SIGNED; + } + } + else if (RT_SUCCESS(rc)) + { + HRESULT hrcWinVerifyTrust; + rc = supR3HardNtViCallWinVerifyTrust(hFile, pwszName, fFlags, pErrInfo, g_pfnWinVerifyTrust, + &hrcWinVerifyTrust); + + /* DLLs signed with special roots, like "Microsoft Digital Media Authority 2005", + may fail here because the root cert is not in the normal certificate stores + (if any). Our verification code has the basics of these certificates included + and can verify them, which is why we end up here instead of in the + VINF_LDRVI_NOT_SIGNED case above. Current workaround is to do as above. + (Intel graphics driver DLLs, like igdusc64.dll. */ + if ( RT_FAILURE(rc) + && hrcWinVerifyTrust == CERT_E_CHAINING + && (fFlags & SUPHNTVI_F_ALLOW_CAT_FILE_VERIFICATION)) + { + rc = supR3HardNtViCallWinVerifyTrustCatFile(hFile, pwszName, fFlags, pErrInfo, g_pfnWinVerifyTrust); + SUP_DPRINTF(("supR3HardNtViCallWinVerifyTrustCatFile -> %d (was CERT_E_CHAINING)\n", rc)); + } + } + else + { + int rc2 = supR3HardNtViCallWinVerifyTrust(hFile, pwszName, fFlags, pErrInfo, g_pfnWinVerifyTrust, NULL); + AssertMsg(RT_FAILURE_NP(rc2), + ("rc=%Rrc, rc2=%Rrc %s", rc, rc2, pErrInfo ? pErrInfo->pszMsg : "<no-err-info>")); + RT_NOREF_PV(rc2); + } + } + + /* Unwind recursion. */ + if (g_iTlsWinVerifyTrustRecursion != UINT32_MAX) + TlsSetValue(g_iTlsWinVerifyTrustRecursion, (void *)0); + else + ASMAtomicWriteU32(&g_idActiveThread, UINT32_MAX); + } + /* + * No can do. + */ + else + SUP_DPRINTF(("Detected WinVerifyTrust recursion: rc=%Rrc '%ls'.\n", rc, pwszName)); + } + else + SUP_DPRINTF(("Detected loader lock ownership: rc=%Rrc '%ls'.\n", rc, pwszName)); + } + return rc; +} + + +/** + * Checks if WinVerifyTrust is callable on the current thread. + * + * Used by the main code to figure whether it makes sense to try revalidate an + * image that hasn't passed thru WinVerifyTrust yet. + * + * @returns true if callable on current thread, false if not. + */ +DECLHIDDEN(bool) supHardenedWinIsWinVerifyTrustCallable(void) +{ + return g_pfnWinVerifyTrust != NULL + && ( g_iTlsWinVerifyTrustRecursion != UINT32_MAX + ? (uintptr_t)TlsGetValue(g_iTlsWinVerifyTrustRecursion) == 0 + : g_idActiveThread != RTNtCurrentThreadId() ); +} + + + +/** + * Initializes g_uNtVerCombined and g_NtVerInfo. + * Called from suplibHardenedWindowsMain and suplibOsInit. + */ +DECLHIDDEN(void) supR3HardenedWinInitVersion(bool fEarly) +{ + /* + * Get the windows version. Use RtlGetVersion as GetVersionExW and + * GetVersion might not be telling the whole truth (8.0 on 8.1 depending on + * the application manifest). + * + * Note! Windows 10 build 14267+ touches BSS when calling RtlGetVersion, so we + * have to use the fallback for the call from the early init code. + */ + OSVERSIONINFOEXW NtVerInfo; + + RT_ZERO(NtVerInfo); + NtVerInfo.dwOSVersionInfoSize = sizeof(RTL_OSVERSIONINFOEXW); + if ( fEarly + || !NT_SUCCESS(RtlGetVersion((PRTL_OSVERSIONINFOW)&NtVerInfo))) + { + RT_ZERO(NtVerInfo); + PPEB pPeb = NtCurrentPeb(); + NtVerInfo.dwMajorVersion = pPeb->OSMajorVersion; + NtVerInfo.dwMinorVersion = pPeb->OSMinorVersion; + NtVerInfo.dwBuildNumber = pPeb->OSBuildNumber; + } + + g_uNtVerCombined = SUP_MAKE_NT_VER_COMBINED(NtVerInfo.dwMajorVersion, NtVerInfo.dwMinorVersion, NtVerInfo.dwBuildNumber, + NtVerInfo.wServicePackMajor, NtVerInfo.wServicePackMinor); +} + +#endif /* IN_RING3 */ + diff --git a/src/VBox/HostDrivers/Support/win/SUPHardenedVerifyProcess-win.cpp b/src/VBox/HostDrivers/Support/win/SUPHardenedVerifyProcess-win.cpp new file mode 100644 index 00000000..ec6b6a0a --- /dev/null +++ b/src/VBox/HostDrivers/Support/win/SUPHardenedVerifyProcess-win.cpp @@ -0,0 +1,2681 @@ +/* $Id: SUPHardenedVerifyProcess-win.cpp $ */ +/** @file + * VirtualBox Support Library/Driver - Hardened Process Verification, Windows. + */ + +/* + * Copyright (C) 2006-2023 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 <https://www.gnu.org/licenses>. + * + * 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 <iprt/nt/nt.h> +# include <ntimage.h> +#else +# include <iprt/nt/nt-and-windows.h> +#endif + +#include <VBox/sup.h> +#include <VBox/err.h> +#include <iprt/alloca.h> +#include <iprt/ctype.h> +#include <iprt/param.h> +#include <iprt/string.h> +#include <iprt/utf16.h> +#include <iprt/zero.h> + +#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_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); + } + 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); + 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; +} + diff --git a/src/VBox/HostDrivers/Support/win/SUPLib-win.cpp b/src/VBox/HostDrivers/Support/win/SUPLib-win.cpp new file mode 100644 index 00000000..dfa590a9 --- /dev/null +++ b/src/VBox/HostDrivers/Support/win/SUPLib-win.cpp @@ -0,0 +1,1002 @@ +/* $Id: SUPLib-win.cpp $ */ +/** @file + * VirtualBox Support Library - Windows NT specific parts. + */ + +/* + * Copyright (C) 2006-2023 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 <https://www.gnu.org/licenses>. + * + * 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 * +*********************************************************************************************************************************/ +#define LOG_GROUP LOG_GROUP_SUP +#ifdef IN_SUP_HARDENED_R3 +# undef DEBUG /* Warning: disables RT_STRICT */ +# undef LOG_DISABLED +# define LOG_DISABLED + /** @todo RTLOGREL_DISABLED */ +# include <iprt/log.h> +# undef LogRelIt +# define LogRelIt(pvInst, fFlags, iGroup, fmtargs) do { } while (0) +#endif + +#define USE_NT_DEVICE_IO_CONTROL_FILE +#include <iprt/nt/nt-and-windows.h> + +#include <VBox/sup.h> +#include <VBox/types.h> +#include <VBox/err.h> +#include <VBox/param.h> +#include <VBox/log.h> +#include <iprt/assert.h> +#ifndef IN_SUP_HARDENED_R3 +# include <iprt/env.h> +# include <iprt/x86.h> +# include <iprt/ldr.h> +#endif +#include <iprt/path.h> +#include <iprt/string.h> +#include "../SUPLibInternal.h" +#include "../SUPDrvIOC.h" +#ifdef VBOX_WITH_HARDENING +# include "win/SUPHardenedVerify-win.h" +#endif + + +/********************************************************************************************************************************* +* Defined Constants And Macros * +*********************************************************************************************************************************/ +/** The support service name. */ +#define SERVICE_NAME "VBoxSup" + + +/********************************************************************************************************************************* +* Internal Functions * +*********************************************************************************************************************************/ +#ifndef IN_SUP_HARDENED_R3 +static int suplibOsCreateService(void); +//unused: static int suplibOsUpdateService(void); +static int suplibOsDeleteService(void); +static int suplibOsStartService(void); +static int suplibOsStopService(void); +#endif +#ifdef USE_NT_DEVICE_IO_CONTROL_FILE +static int suplibConvertNtStatus(NTSTATUS rcNt); +#else +static int suplibConvertWin32Err(int); +#endif + + +/********************************************************************************************************************************* +* Global Variables * +*********************************************************************************************************************************/ +static bool g_fHardenedVerifyInited = false; + + +DECLHIDDEN(int) suplibOsHardenedVerifyInit(void) +{ + if (!g_fHardenedVerifyInited) + { +#if defined(VBOX_WITH_HARDENING) && !defined(IN_SUP_HARDENED_R3) && !defined(IN_SUP_R3_STATIC) + supR3HardenedWinInitVersion(false /*fEarly*/); + int rc = supHardenedWinInitImageVerifier(NULL); + if (RT_FAILURE(rc)) + return rc; + supR3HardenedWinResolveVerifyTrustApiAndHookThreadCreation(NULL); +#endif + g_fHardenedVerifyInited = true; + } + return VINF_SUCCESS; +} + + +DECLHIDDEN(int) suplibOsHardenedVerifyTerm(void) +{ + /** @todo free resources... */ + return VINF_SUCCESS; +} + + +DECLHIDDEN(int) suplibOsInit(PSUPLIBDATA pThis, bool fPreInited, uint32_t fFlags, SUPINITOP *penmWhat, PRTERRINFO pErrInfo) +{ + /* + * Make sure the image verifier is fully initialized. + */ + int rc = suplibOsHardenedVerifyInit(); + if (RT_FAILURE(rc)) + return RTErrInfoSetF(pErrInfo, rc, "suplibOsHardenedVerifyInit failed: %Rrc", rc); + + /* + * Done if of pre-inited. + */ + if (fPreInited) + { +#if defined(VBOX_WITH_HARDENING) && !defined(IN_SUP_HARDENED_R3) +# ifdef IN_SUP_R3_STATIC + return VERR_NOT_SUPPORTED; +# else + return VINF_SUCCESS; +# endif +#else + return VINF_SUCCESS; +#endif + } + +#if !defined(IN_SUP_HARDENED_R3) + /* + * Driverless? + */ + if (fFlags & SUPR3INIT_F_DRIVERLESS) + { + pThis->fDriverless = true; + return VINF_SUCCESS; + } +#endif + + /* + * Try open the device. + */ +#ifndef IN_SUP_HARDENED_R3 + uint32_t cTry = 0; +#endif + HANDLE hDevice; + for (;;) + { + IO_STATUS_BLOCK Ios = RTNT_IO_STATUS_BLOCK_INITIALIZER; + + static const WCHAR s_wszName[] = L"\\Device\\VBoxDrvU"; + UNICODE_STRING NtName; + NtName.Buffer = (PWSTR)s_wszName; + NtName.Length = sizeof(s_wszName) - sizeof(WCHAR) * (fFlags & SUPR3INIT_F_UNRESTRICTED ? 2 : 1); + NtName.MaximumLength = NtName.Length; + + OBJECT_ATTRIBUTES ObjAttr; + InitializeObjectAttributes(&ObjAttr, &NtName, OBJ_CASE_INSENSITIVE, NULL /*hRootDir*/, NULL /*pSecDesc*/); + + hDevice = RTNT_INVALID_HANDLE_VALUE; + + NTSTATUS rcNt = NtCreateFile(&hDevice, + GENERIC_READ | GENERIC_WRITE, /* No SYNCHRONIZE. */ + &ObjAttr, + &Ios, + NULL /* Allocation Size*/, + FILE_ATTRIBUTE_NORMAL, + FILE_SHARE_READ | FILE_SHARE_WRITE, + FILE_OPEN, + FILE_NON_DIRECTORY_FILE, /* No FILE_SYNCHRONOUS_IO_NONALERT! */ + NULL /*EaBuffer*/, + 0 /*EaLength*/); + if (NT_SUCCESS(rcNt)) + rcNt = Ios.Status; + if (NT_SUCCESS(rcNt)) + { + /* + * We're good. + */ + pThis->hDevice = hDevice; + pThis->fUnrestricted = RT_BOOL(fFlags & SUPR3INIT_F_UNRESTRICTED); + return VINF_SUCCESS; + } + +#ifndef IN_SUP_HARDENED_R3 + /* + * Failed to open, try starting the service and reopen the device + * exactly once. + */ + if (cTry == 0 && !NT_SUCCESS(rcNt)) + { + cTry++; + suplibOsStartService(); + continue; + } +#endif + + /* + * Translate the error code. + */ + switch (rcNt) + { + /** @todo someone must test what is actually returned. */ + case STATUS_DEVICE_DOES_NOT_EXIST: + case STATUS_DEVICE_NOT_CONNECTED: + //case ERROR_BAD_DEVICE: + case STATUS_DEVICE_REMOVED: + //case ERROR_DEVICE_NOT_AVAILABLE: + rc = VERR_VM_DRIVER_LOAD_ERROR; + break; + case STATUS_OBJECT_PATH_NOT_FOUND: + case STATUS_NO_SUCH_DEVICE: + case STATUS_NO_SUCH_FILE: + case STATUS_OBJECT_NAME_NOT_FOUND: + rc = VERR_VM_DRIVER_NOT_INSTALLED; + break; + case STATUS_ACCESS_DENIED: + case STATUS_SHARING_VIOLATION: + rc = VERR_VM_DRIVER_NOT_ACCESSIBLE; + break; + case STATUS_UNSUCCESSFUL: + rc = VERR_SUPLIB_NT_PROCESS_UNTRUSTED_0; + break; + case STATUS_TRUST_FAILURE: + rc = VERR_SUPLIB_NT_PROCESS_UNTRUSTED_1; + break; + case STATUS_TOO_LATE: + rc = VERR_SUPDRV_HARDENING_EVIL_HANDLE; + break; + default: + if (SUP_NT_STATUS_IS_VBOX(rcNt)) /* See VBoxDrvNtErr2NtStatus. */ + rc = SUP_NT_STATUS_TO_VBOX(rcNt); + else + rc = VERR_VM_DRIVER_OPEN_ERROR; + break; + } + +#ifdef IN_SUP_HARDENED_R3 + /* + * Get more details from VBoxDrvErrorInfo if present. + */ + if (pErrInfo && pErrInfo->cbMsg > 32) + { + /* Prefix. */ + size_t cchPrefix; + if (RTErrIsKnown(rc)) + cchPrefix = RTStrPrintf(pErrInfo->pszMsg, pErrInfo->cbMsg / 2, "Integrity error (%#x/%Rrc): ", rcNt, rc); + else + cchPrefix = RTStrPrintf(pErrInfo->pszMsg, pErrInfo->cbMsg / 2, "Integrity error (%#x/%d): ", rcNt, rc); + + /* Get error info. */ + supR3HardenedWinReadErrorInfoDevice(pErrInfo->pszMsg + cchPrefix, pErrInfo->cbMsg - cchPrefix, ""); + if (pErrInfo->pszMsg[cchPrefix] != '\0') + { + pErrInfo->fFlags |= RTERRINFO_FLAGS_SET; + pErrInfo->rc = rc; + *penmWhat = kSupInitOp_Integrity; + } + else + pErrInfo->pszMsg[0] = '\0'; + } + +#else + RT_NOREF1(penmWhat); + + /* + * Do fallback. + */ + if ( (fFlags & SUPR3INIT_F_DRIVERLESS_MASK) + && rcNt != -1 /** @todo */ ) + { + LogRel(("Failed to open '%.*ls' rc=%Rrc rcNt=%#x - Switching to driverless mode.\n", + NtName.Length / sizeof(WCHAR), NtName.Buffer, rc, rcNt)); + pThis->fDriverless = true; + return VINF_SUCCESS; + } +#endif + return rc; + } +} + + +#ifndef IN_SUP_HARDENED_R3 + +DECLHIDDEN(int) suplibOsInstall(void) +{ + int rc = suplibOsCreateService(); + if (RT_SUCCESS(rc)) + { + int rc2 = suplibOsStartService(); + if (rc2 != VINF_SUCCESS) + rc = rc2; + } + return rc; +} + + +DECLHIDDEN(int) suplibOsUninstall(void) +{ + int rc = suplibOsStopService(); + if (RT_SUCCESS(rc)) + rc = suplibOsDeleteService(); + return rc; +} + + +/** + * Creates the service. + * + * @returns VBox status code. + * @retval VWRN_ALREADY_EXISTS if it already exists. + */ +static int suplibOsCreateService(void) +{ + /* + * Assume it didn't exist, so we'll create the service. + */ + int rc; + SC_HANDLE hSMgrCreate = OpenSCManager(NULL, NULL, SERVICE_CHANGE_CONFIG); + DWORD dwErr = GetLastError(); + AssertMsg(hSMgrCreate, ("OpenSCManager(,,create) failed dwErr=%d\n", dwErr)); + if (hSMgrCreate != NULL) + { + char szDriver[RTPATH_MAX]; + rc = RTPathExecDir(szDriver, sizeof(szDriver) - sizeof("\\VBoxSup.sys")); + if (RT_SUCCESS(rc)) + { + strcat(szDriver, "\\VBoxSup.sys"); + SC_HANDLE hService = CreateService(hSMgrCreate, + SERVICE_NAME, + "VBox Support Driver", + SERVICE_QUERY_STATUS, + SERVICE_KERNEL_DRIVER, + SERVICE_DEMAND_START, + SERVICE_ERROR_NORMAL, + szDriver, + NULL, NULL, NULL, NULL, NULL); + dwErr = GetLastError(); + if (hService) + { + CloseServiceHandle(hService); + rc = VINF_SUCCESS; + } + else if (dwErr == ERROR_SERVICE_EXISTS) + rc = VWRN_ALREADY_EXISTS; + else + { + AssertMsgFailed(("CreateService failed! dwErr=%Rwa szDriver=%s\n", dwErr, szDriver)); + rc = RTErrConvertFromWin32(dwErr); + } + } + CloseServiceHandle(hSMgrCreate); + } + else + rc = RTErrConvertFromWin32(GetLastError()); + return rc; +} + + +/** + * Stops a possibly running service. + * + * @returns VBox status code. + */ +static int suplibOsStopService(void) +{ + /* + * Assume it didn't exist, so we'll create the service. + */ + int rc; + SC_HANDLE hSMgr = OpenSCManager(NULL, NULL, SERVICE_STOP | SERVICE_QUERY_STATUS); + DWORD dwErr = GetLastError(); + AssertMsg(hSMgr, ("OpenSCManager(,,delete) failed dwErr=%d\n", dwErr)); + if (hSMgr) + { + SC_HANDLE hService = OpenService(hSMgr, SERVICE_NAME, SERVICE_STOP | SERVICE_QUERY_STATUS); + if (hService) + { + /* + * Stop the service. + */ + SERVICE_STATUS Status; + QueryServiceStatus(hService, &Status); + if (Status.dwCurrentState == SERVICE_STOPPED) + rc = VINF_SUCCESS; + else if (ControlService(hService, SERVICE_CONTROL_STOP, &Status)) + { + int iWait = 100; + while (Status.dwCurrentState == SERVICE_STOP_PENDING && iWait-- > 0) + { + Sleep(100); + QueryServiceStatus(hService, &Status); + } + if (Status.dwCurrentState == SERVICE_STOPPED) + rc = VINF_SUCCESS; + else + { + AssertMsgFailed(("Failed to stop service. status=%d\n", Status.dwCurrentState)); + rc = VERR_GENERAL_FAILURE; + } + } + else + { + dwErr = GetLastError(); + if ( Status.dwCurrentState == SERVICE_STOP_PENDING + && dwErr == ERROR_SERVICE_CANNOT_ACCEPT_CTRL) + rc = VERR_RESOURCE_BUSY; /* better than VERR_GENERAL_FAILURE */ + else + { + AssertMsgFailed(("ControlService failed with dwErr=%Rwa. status=%d\n", dwErr, Status.dwCurrentState)); + rc = RTErrConvertFromWin32(dwErr); + } + } + CloseServiceHandle(hService); + } + else + { + dwErr = GetLastError(); + if (GetLastError() == ERROR_SERVICE_DOES_NOT_EXIST) + rc = VINF_SUCCESS; + else + { + AssertMsgFailed(("OpenService failed dwErr=%Rwa\n", dwErr)); + rc = RTErrConvertFromWin32(dwErr); + } + } + CloseServiceHandle(hSMgr); + } + else + rc = RTErrConvertFromWin32(dwErr); + return rc; +} + + +/** + * Deletes the service. + * + * @returns VBox status code. + */ +int suplibOsDeleteService(void) +{ + int rcRet = VINF_SUCCESS; + SC_HANDLE hSMgr = OpenSCManager(NULL, NULL, SERVICE_CHANGE_CONFIG); + DWORD dwErr = GetLastError(); + AssertMsg(hSMgr, ("OpenSCManager(,,delete) failed rc=%d\n", dwErr)); + if (hSMgr) + { + /* + * Old service name. + */ + SC_HANDLE hService = OpenService(hSMgr, "VBoxDrv", DELETE); + if (hService) + { + if (!DeleteService(hService)) + { + dwErr = GetLastError(); + AssertMsgFailed(("DeleteService failed for VBoxDrv dwErr=%Rwa\n", dwErr)); + rcRet = RTErrConvertFromWin32(dwErr); + } + CloseServiceHandle(hService); + } + else + { + dwErr = GetLastError(); + if (dwErr != ERROR_SERVICE_DOES_NOT_EXIST) + { + AssertMsgFailed(("OpenService failed for VBoxDrv dwErr=%Rwa\n", dwErr)); + rcRet = RTErrConvertFromWin32(dwErr); + } + } + + /* + * The new service. + */ + hService = OpenService(hSMgr, SERVICE_NAME, DELETE); + if (hService) + { + if (!DeleteService(hService)) + { + dwErr = GetLastError(); + AssertMsgFailed(("DeleteService for " SERVICE_NAME " failed dwErr=%Rwa\n", dwErr)); + rcRet = RTErrConvertFromWin32(dwErr); + } + CloseServiceHandle(hService); + } + else + { + dwErr = GetLastError(); + if (dwErr != ERROR_SERVICE_DOES_NOT_EXIST) + { + AssertMsgFailed(("OpenService failed for " SERVICE_NAME " dwErr=%Rwa\n", dwErr)); + rcRet = RTErrConvertFromWin32(dwErr); + } + } + CloseServiceHandle(hSMgr); + } + else + rcRet = RTErrConvertFromWin32(dwErr); + return rcRet; +} + +#if 0 +/** + * Creates the service. + * + * @returns 0 on success. + * @returns -1 on failure. + */ +static int suplibOsUpdateService(void) +{ + /* + * Assume it didn't exist, so we'll create the service. + */ + SC_HANDLE hSMgr = OpenSCManager(NULL, NULL, SERVICE_CHANGE_CONFIG); + DWORD LastError = GetLastError(); NOREF(LastError); + AssertMsg(hSMgr, ("OpenSCManager(,,delete) failed LastError=%Rwa\n", LastError)); + if (hSMgr) + { + SC_HANDLE hService = OpenService(hSMgr, SERVICE_NAME, SERVICE_CHANGE_CONFIG); + if (hService) + { + char szDriver[RTPATH_MAX]; + int rc = RTPathExecDir(szDriver, sizeof(szDriver) - sizeof("\\VBoxSup.sys")); + if (RT_SUCCESS(rc)) + { + strcat(szDriver, "\\VBoxSup.sys"); + + SC_LOCK hLock = LockServiceDatabase(hSMgr); + if (ChangeServiceConfig(hService, + SERVICE_KERNEL_DRIVER, + SERVICE_DEMAND_START, + SERVICE_ERROR_NORMAL, + szDriver, + NULL, NULL, NULL, NULL, NULL, NULL)) + { + + UnlockServiceDatabase(hLock); + CloseServiceHandle(hService); + CloseServiceHandle(hSMgr); + return 0; + } + else + { + DWORD LastError = GetLastError(); NOREF(LastError); + AssertMsgFailed(("ChangeServiceConfig failed LastError=%Rwa\n", LastError)); + } + } + UnlockServiceDatabase(hLock); + CloseServiceHandle(hService); + } + else + { + DWORD LastError = GetLastError(); NOREF(LastError); + AssertMsgFailed(("OpenService failed LastError=%Rwa\n", LastError)); + } + CloseServiceHandle(hSMgr); + } + return -1; +} +#endif + + +/** + * Attempts to start the service, creating it if necessary. + * + * @returns VBox status code. + */ +static int suplibOsStartService(void) +{ + /* + * Check if the driver service is there. + */ + SC_HANDLE hSMgr = OpenSCManager(NULL, NULL, SERVICE_QUERY_STATUS | SERVICE_START); + if (hSMgr == NULL) + { + DWORD dwErr = GetLastError(); + AssertMsgFailed(("couldn't open service manager in SERVICE_QUERY_CONFIG | SERVICE_QUERY_STATUS mode! (dwErr=%d)\n", dwErr)); + return RTErrConvertFromWin32(dwErr); + } + + /* + * Try open our service to check it's status. + */ + SC_HANDLE hService = OpenService(hSMgr, SERVICE_NAME, SERVICE_QUERY_STATUS | SERVICE_START); + if (!hService) + { + /* + * Create the service. + */ + int rc = suplibOsCreateService(); + if (RT_FAILURE(rc)) + return rc; + + /* + * Try open the service. + */ + hService = OpenService(hSMgr, SERVICE_NAME, SERVICE_QUERY_STATUS | SERVICE_START); + } + + /* + * Check if open and on demand create succeeded. + */ + int rc; + if (hService) + { + + /* + * Query service status to see if we need to start it or not. + */ + SERVICE_STATUS Status; + BOOL fRc = QueryServiceStatus(hService, &Status); + Assert(fRc); NOREF(fRc); + if (Status.dwCurrentState == SERVICE_RUNNING) + rc = VINF_ALREADY_INITIALIZED; + else + { + if (Status.dwCurrentState == SERVICE_START_PENDING) + rc = VINF_SUCCESS; + else + { + /* + * Start it. + */ + if (StartService(hService, 0, NULL)) + rc = VINF_SUCCESS; + else + { + DWORD dwErr = GetLastError(); + AssertMsg(fRc, ("StartService failed with dwErr=%Rwa\n", dwErr)); + rc = RTErrConvertFromWin32(dwErr); + } + } + + /* + * Wait for the service to finish starting. + * We'll wait for 10 seconds then we'll give up. + */ + QueryServiceStatus(hService, &Status); + if (Status.dwCurrentState == SERVICE_START_PENDING) + { + int iWait; + for (iWait = 100; iWait > 0 && Status.dwCurrentState == SERVICE_START_PENDING; iWait--) + { + Sleep(100); + QueryServiceStatus(hService, &Status); + } + DWORD dwErr = GetLastError(); NOREF(dwErr); + AssertMsg(Status.dwCurrentState != SERVICE_RUNNING, + ("Failed to start. dwErr=%Rwa iWait=%d status=%d\n", dwErr, iWait, Status.dwCurrentState)); + } + + if (Status.dwCurrentState == SERVICE_RUNNING) + rc = VINF_SUCCESS; + else if (RT_SUCCESS_NP(rc)) + rc = VERR_GENERAL_FAILURE; + } + + /* + * Close open handles. + */ + CloseServiceHandle(hService); + } + else + { + DWORD dwErr = GetLastError(); + AssertMsgFailed(("OpenService failed! LastError=%Rwa\n", dwErr)); + rc = RTErrConvertFromWin32(dwErr); + } + if (!CloseServiceHandle(hSMgr)) + AssertFailed(); + + return rc; +} + +#endif /* !IN_SUP_HARDENED_R3 */ + +DECLHIDDEN(int) suplibOsTerm(PSUPLIBDATA pThis) +{ + /* + * Check if we're inited at all. + */ + if (pThis->hDevice != NULL) + { + NTSTATUS rcNt = NtClose((HANDLE)pThis->hDevice); + Assert(NT_SUCCESS(rcNt)); RT_NOREF(rcNt); + pThis->hDevice = NIL_RTFILE; /* yes, that's right */ + } + + return VINF_SUCCESS; +} + +#ifndef IN_SUP_HARDENED_R3 + +DECLHIDDEN(int) suplibOsIOCtl(PSUPLIBDATA pThis, uintptr_t uFunction, void *pvReq, size_t cbReq) +{ + RT_NOREF1(cbReq); + + /* + * Issue the device I/O control. + */ + PSUPREQHDR pHdr = (PSUPREQHDR)pvReq; + Assert(cbReq == RT_MAX(pHdr->cbIn, pHdr->cbOut)); +# ifdef USE_NT_DEVICE_IO_CONTROL_FILE + IO_STATUS_BLOCK Ios = RTNT_IO_STATUS_BLOCK_INITIALIZER; + NTSTATUS rcNt = NtDeviceIoControlFile((HANDLE)pThis->hDevice, NULL /*hEvent*/, NULL /*pfnApc*/, NULL /*pvApcCtx*/, &Ios, + (ULONG)uFunction, + pvReq /*pvInput */, pHdr->cbIn /* cbInput */, + pvReq /*pvOutput*/, pHdr->cbOut /* cbOutput */); + if (NT_SUCCESS(rcNt)) + { + if (NT_SUCCESS(Ios.Status)) + return VINF_SUCCESS; + rcNt = Ios.Status; + } + return suplibConvertNtStatus(rcNt); + +# else + DWORD cbReturned = (ULONG)pHdr->cbOut; + if (DeviceIoControl((HANDLE)pThis->hDevice, uFunction, pvReq, pHdr->cbIn, pvReq, cbReturned, &cbReturned, NULL)) + return 0; + return suplibConvertWin32Err(GetLastError()); +# endif +} + + +DECLHIDDEN(int) suplibOsIOCtlFast(PSUPLIBDATA pThis, uintptr_t uFunction, uintptr_t idCpu) +{ + /* + * Issue device I/O control. + */ +# ifdef USE_NT_DEVICE_IO_CONTROL_FILE + IO_STATUS_BLOCK Ios = RTNT_IO_STATUS_BLOCK_INITIALIZER; + NTSTATUS rcNt = NtDeviceIoControlFile((HANDLE)pThis->hDevice, NULL /*hEvent*/, NULL /*pfnApc*/, NULL /*pvApcCtx*/, &Ios, + (ULONG)uFunction, + NULL /*pvInput */, 0 /* cbInput */, + (PVOID)idCpu /*pvOutput*/, 0 /* cbOutput */); + if (NT_SUCCESS(rcNt)) + { + if (NT_SUCCESS(Ios.Status)) + return VINF_SUCCESS; + rcNt = Ios.Status; + } + return suplibConvertNtStatus(rcNt); +# else + DWORD cbReturned = 0; + if (DeviceIoControl((HANDLE)pThis->hDevice, uFunction, NULL, 0, (LPVOID)idCpu, 0, &cbReturned, NULL)) + return VINF_SUCCESS; + return suplibConvertWin32Err(GetLastError()); +# endif +} + + +DECLHIDDEN(int) suplibOsPageAlloc(PSUPLIBDATA pThis, size_t cPages, uint32_t fFlags, void **ppvPages) +{ + RT_NOREF(pThis, fFlags); + + /* + * Do some one-time init here wrt large pages. + * + * Large pages requires the SeLockMemoryPrivilege, which by default (Win10, + * Win11) isn't even enabled and must be gpedit'ed to be adjustable here. + */ + if (!(cPages & 511) && (fFlags & SUP_PAGE_ALLOC_F_LARGE_PAGES)) + { + static int volatile s_fCanDoLargePages = -1; + int fCanDoLargePages = s_fCanDoLargePages; + if (s_fCanDoLargePages != -1) + { /* likely */ } + else if (RTEnvExistsUtf8("VBOX_DO_NOT_USE_LARGE_PAGES")) /** @todo add flag for this instead? */ + s_fCanDoLargePages = fCanDoLargePages = 0; + else + { + HANDLE hToken = NULL; + if (OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY | TOKEN_ADJUST_PRIVILEGES, &hToken)) + { + union + { + TOKEN_PRIVILEGES s; + uint8_t abPadding[RT_UOFFSETOF(TOKEN_PRIVILEGES, Privileges) + sizeof(LUID_AND_ATTRIBUTES)]; + } Privileges; + RT_ZERO(Privileges); + Privileges.s.PrivilegeCount = 1; + Privileges.s.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED; + if (LookupPrivilegeValueW(NULL, L"SeLockMemoryPrivilege", &Privileges.s.Privileges[0].Luid)) + AdjustTokenPrivileges(hToken, FALSE, &Privileges.s, 0, NULL, NULL); + else + AssertFailed(); + CloseHandle(hToken); + } + else + AssertFailed(); + s_fCanDoLargePages = fCanDoLargePages = -2; + } + + /* + * Try allocate with large pages. + */ + if (fCanDoLargePages != 0) + { + *ppvPages = VirtualAlloc(NULL, (size_t)cPages << PAGE_SHIFT, MEM_COMMIT | MEM_LARGE_PAGES, PAGE_EXECUTE_READWRITE); + if (*ppvPages) + { + if (fCanDoLargePages == -2) + { + s_fCanDoLargePages = 1; + LogRel(("SUPLib: MEM_LARGE_PAGES works!\n")); + } + LogRel2(("SUPLib: MEM_LARGE_PAGES for %p LB %p\n", *ppvPages, (size_t)cPages << PAGE_SHIFT)); + return VINF_SUCCESS; + } + + /* This can happen if the above AdjustTokenPrivileges failed (non-admin + user), or if the privilege isn't present in the token (need gpedit). */ + if (GetLastError() == ERROR_PRIVILEGE_NOT_HELD) + { + LogRel(("SUPLib: MEM_LARGE_PAGES privilege not held.\n")); + s_fCanDoLargePages = 0; + } + else + LogRel2(("SUPLib: MEM_LARGE_PAGES allocation failed with odd status: %u\n", GetLastError())); + } + } + + /* + * Do a regular allocation w/o large pages. + */ + *ppvPages = VirtualAlloc(NULL, (size_t)cPages << PAGE_SHIFT, MEM_COMMIT, PAGE_EXECUTE_READWRITE); + if (*ppvPages) + return VINF_SUCCESS; + return RTErrConvertFromWin32(GetLastError()); +} + + +DECLHIDDEN(int) suplibOsPageFree(PSUPLIBDATA pThis, void *pvPages, size_t /* cPages */) +{ + NOREF(pThis); + if (VirtualFree(pvPages, 0, MEM_RELEASE)) + return VINF_SUCCESS; + return RTErrConvertFromWin32(GetLastError()); +} + + +DECLHIDDEN(bool) suplibOsIsNemSupportedWhenNoVtxOrAmdV(void) +{ +# if ARCH_BITS == 64 + /* + * Check that we're in a VM. + */ + if (!ASMHasCpuId()) + return false; + if (!RTX86IsValidStdRange(ASMCpuId_EAX(0))) + return false; + if (!(ASMCpuId_ECX(1) & X86_CPUID_FEATURE_ECX_HVP)) + return false; + + /* + * Try load WinHvPlatform and resolve API for checking. + * Note! The two size_t arguments and the ssize_t one are all too big, but who cares. + */ + RTLDRMOD hLdrMod = NIL_RTLDRMOD; + int rc = RTLdrLoadSystem("WinHvPlatform.dll", false, &hLdrMod); + if (RT_FAILURE(rc)) + return false; + + bool fRet = false; + typedef HRESULT (WINAPI *PFNWHVGETCAPABILITY)(ssize_t, void *, size_t, size_t *); + PFNWHVGETCAPABILITY pfnWHvGetCapability = (PFNWHVGETCAPABILITY)RTLdrGetFunction(hLdrMod, "WHvGetCapability"); + if (pfnWHvGetCapability) + { + /* + * Query the API. + */ + union + { + BOOL fHypervisorPresent; + uint64_t u64Padding; + } Caps; + RT_ZERO(Caps); + size_t cbRetIgn = 0; + HRESULT hrc = pfnWHvGetCapability(0 /*WHvCapabilityCodeHypervisorPresent*/, &Caps, sizeof(Caps), &cbRetIgn); + if (SUCCEEDED(hrc) && Caps.fHypervisorPresent) + fRet = true; + } + + RTLdrClose(hLdrMod); + return fRet; +# else + return false; +#endif +} + + +# ifndef USE_NT_DEVICE_IO_CONTROL_FILE +/** + * Converts a supdrv win32 error code to an IPRT status code. + * + * @returns corresponding IPRT error code. + * @param rc Win32 error code. + */ +static int suplibConvertWin32Err(int rc) +{ + /* Conversion program (link with ntdll.lib from ddk): + #define _WIN32_WINNT 0x0501 + #include <iprt/win/windows.h> + #include <ntstatus.h> + #include <winternl.h> + #include <stdio.h> + + int main() + { + #define CONVERT(a) printf(#a " %#x -> %d\n", a, RtlNtStatusToDosError((a))) + CONVERT(STATUS_SUCCESS); + CONVERT(STATUS_NOT_SUPPORTED); + CONVERT(STATUS_INVALID_PARAMETER); + CONVERT(STATUS_UNKNOWN_REVISION); + CONVERT(STATUS_INVALID_HANDLE); + CONVERT(STATUS_INVALID_ADDRESS); + CONVERT(STATUS_NOT_LOCKED); + CONVERT(STATUS_IMAGE_ALREADY_LOADED); + CONVERT(STATUS_ACCESS_DENIED); + CONVERT(STATUS_REVISION_MISMATCH); + + return 0; + } + */ + + switch (rc) + { + //case 0: return STATUS_SUCCESS; + case 0: return VINF_SUCCESS; + case ERROR_NOT_SUPPORTED: return VERR_GENERAL_FAILURE; + case ERROR_INVALID_PARAMETER: return VERR_INVALID_PARAMETER; + case ERROR_UNKNOWN_REVISION: return VERR_INVALID_MAGIC; + case ERROR_INVALID_HANDLE: return VERR_INVALID_HANDLE; + case ERROR_UNEXP_NET_ERR: return VERR_INVALID_POINTER; + case ERROR_NOT_LOCKED: return VERR_LOCK_FAILED; + case ERROR_SERVICE_ALREADY_RUNNING: return VERR_ALREADY_LOADED; + case ERROR_ACCESS_DENIED: return VERR_PERMISSION_DENIED; + case ERROR_REVISION_MISMATCH: return VERR_VERSION_MISMATCH; + } + + /* fall back on the default conversion. */ + return RTErrConvertFromWin32(rc); +} +# else +/** + * Reverse of VBoxDrvNtErr2NtStatus + * returns VBox status code. + * @param rcNt NT status code. + */ +static int suplibConvertNtStatus(NTSTATUS rcNt) +{ + switch (rcNt) + { + case STATUS_SUCCESS: return VINF_SUCCESS; + case STATUS_NOT_SUPPORTED: return VERR_GENERAL_FAILURE; + case STATUS_INVALID_PARAMETER: return VERR_INVALID_PARAMETER; + case STATUS_UNKNOWN_REVISION: return VERR_INVALID_MAGIC; + case STATUS_INVALID_HANDLE: return VERR_INVALID_HANDLE; + case STATUS_INVALID_ADDRESS: return VERR_INVALID_POINTER; + case STATUS_NOT_LOCKED: return VERR_LOCK_FAILED; + case STATUS_IMAGE_ALREADY_LOADED: return VERR_ALREADY_LOADED; + case STATUS_ACCESS_DENIED: return VERR_PERMISSION_DENIED; + case STATUS_REVISION_MISMATCH: return VERR_VERSION_MISMATCH; + } + + /* See VBoxDrvNtErr2NtStatus. */ + if (SUP_NT_STATUS_IS_VBOX(rcNt)) + return SUP_NT_STATUS_TO_VBOX(rcNt); + + /* Fall back on IPRT for the rest. */ + return RTErrConvertFromNtStatus(rcNt); +} +# endif + +#endif /* !IN_SUP_HARDENED_R3 */ + diff --git a/src/VBox/HostDrivers/Support/win/SUPR0IdcClient-win.c b/src/VBox/HostDrivers/Support/win/SUPR0IdcClient-win.c new file mode 100644 index 00000000..73fe259d --- /dev/null +++ b/src/VBox/HostDrivers/Support/win/SUPR0IdcClient-win.c @@ -0,0 +1,167 @@ +/* $Id: SUPR0IdcClient-win.c $ */ +/** @file + * VirtualBox Support Driver - IDC Client Lib, Windows Specific Code. + */ + +/* + * Copyright (C) 2008-2023 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 <https://www.gnu.org/licenses>. + * + * 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 * +*********************************************************************************************************************************/ +#include "../SUPR0IdcClientInternal.h" +#include <iprt/errcore.h> + + +/********************************************************************************************************************************* +* Defined Constants And Macros * +*********************************************************************************************************************************/ +/** NT Device name. */ +#define DEVICE_NAME_NT L"\\Device\\VBoxDrv" + + +/** + * Internal I/O Control call worker. + * + * @returns VBox status code. + * @param pDeviceObject The device object to call. + * @param pFileObject The file object for the connection. + * @param uReq The request. + * @param pReq The request packet. + */ +static int supR0IdcNtCallInternal(PDEVICE_OBJECT pDeviceObject, PFILE_OBJECT pFileObject, uint32_t uReq, PSUPDRVIDCREQHDR pReq) +{ + int rc; + IO_STATUS_BLOCK IoStatusBlock; + KEVENT Event; + PIRP pIrp; + NTSTATUS rcNt; + + /* + * Build the request. + */ + KeInitializeEvent(&Event, NotificationEvent, FALSE); + pIrp = IoBuildDeviceIoControlRequest(uReq, /* IoControlCode */ + pDeviceObject, + pReq, /* InputBuffer */ + pReq->cb, /* InputBufferLength */ + pReq, /* OutputBuffer */ + pReq->cb, /* OutputBufferLength */ + TRUE, /* InternalDeviceIoControl (=> IRP_MJ_INTERNAL_DEVICE_CONTROL) */ + &Event, /* Event */ + &IoStatusBlock); /* IoStatusBlock */ + if (pIrp) + { + IoGetNextIrpStackLocation(pIrp)->FileObject = pFileObject; + + /* + * Call the driver, wait for an async request to complete (should never happen). + */ + rcNt = IoCallDriver(pDeviceObject, pIrp); + if (rcNt == STATUS_PENDING) + { + rcNt = KeWaitForSingleObject(&Event, /* Object */ + Executive, /* WaitReason */ + KernelMode, /* WaitMode */ + FALSE, /* Alertable */ + NULL); /* TimeOut */ + rcNt = IoStatusBlock.Status; + } + if (NT_SUCCESS(rcNt)) + rc = pReq->rc; + else + rc = RTErrConvertFromNtStatus(rcNt); + } + else + rc = VERR_NO_MEMORY; + return rc; +} + + +int VBOXCALL supR0IdcNativeOpen(PSUPDRVIDCHANDLE pHandle, PSUPDRVIDCREQCONNECT pReq) +{ + PDEVICE_OBJECT pDeviceObject = NULL; + PFILE_OBJECT pFileObject = NULL; + UNICODE_STRING wszDeviceName; + NTSTATUS rcNt; + int rc; + + /* + * Get the device object pointer. + */ + RtlInitUnicodeString(&wszDeviceName, DEVICE_NAME_NT); + rcNt = IoGetDeviceObjectPointer(&wszDeviceName, FILE_ALL_ACCESS, &pFileObject, &pDeviceObject); + if (NT_SUCCESS(rcNt)) + { + /* + * Make the connection call. + */ + rc = supR0IdcNtCallInternal(pDeviceObject, pFileObject, SUPDRV_IDC_REQ_CONNECT, &pReq->Hdr); + if (RT_SUCCESS(rc)) + { + pHandle->s.pDeviceObject = pDeviceObject; + pHandle->s.pFileObject = pFileObject; + return rc; + } + + /* only the file object. */ + ObDereferenceObject(pFileObject); + } + else + rc = RTErrConvertFromNtStatus(rcNt); + + pHandle->s.pDeviceObject = NULL; + pHandle->s.pFileObject = NULL; + return rc; +} + + +int VBOXCALL supR0IdcNativeClose(PSUPDRVIDCHANDLE pHandle, PSUPDRVIDCREQHDR pReq) +{ + PFILE_OBJECT pFileObject = pHandle->s.pFileObject; + int rc = supR0IdcNtCallInternal(pHandle->s.pDeviceObject, pFileObject, SUPDRV_IDC_REQ_DISCONNECT, pReq); + if (RT_SUCCESS(rc)) + { + pHandle->s.pDeviceObject = NULL; + pHandle->s.pFileObject = NULL; + ObDereferenceObject(pFileObject); + } + + return rc; +} + + +int VBOXCALL supR0IdcNativeCall(PSUPDRVIDCHANDLE pHandle, uint32_t uReq, PSUPDRVIDCREQHDR pReq) +{ + return supR0IdcNtCallInternal(pHandle->s.pDeviceObject, pHandle->s.pFileObject, uReq, pReq); +} + diff --git a/src/VBox/HostDrivers/Support/win/SUPR3HardenedMain-win.cpp b/src/VBox/HostDrivers/Support/win/SUPR3HardenedMain-win.cpp new file mode 100644 index 00000000..a7dccfd9 --- /dev/null +++ b/src/VBox/HostDrivers/Support/win/SUPR3HardenedMain-win.cpp @@ -0,0 +1,7031 @@ +/* $Id: SUPR3HardenedMain-win.cpp $ */ +/** @file + * VirtualBox Support Library - Hardened main(), windows bits. + */ + +/* + * Copyright (C) 2006-2023 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 <https://www.gnu.org/licenses>. + * + * 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 * +*********************************************************************************************************************************/ +#include <iprt/nt/nt-and-windows.h> +#include <AccCtrl.h> +#include <AclApi.h> +#ifndef PROCESS_SET_LIMITED_INFORMATION +# define PROCESS_SET_LIMITED_INFORMATION 0x2000 +#endif +#ifndef LOAD_LIBRARY_SEARCH_APPLICATION_DIR +# define LOAD_LIBRARY_SEARCH_DLL_LOAD_DIR UINT32_C(0x100) +# define LOAD_LIBRARY_SEARCH_APPLICATION_DIR UINT32_C(0x200) +# define LOAD_LIBRARY_SEARCH_USER_DIRS UINT32_C(0x400) +# define LOAD_LIBRARY_SEARCH_SYSTEM32 UINT32_C(0x800) +#endif + +#include <VBox/sup.h> +#include <VBox/err.h> +#include <VBox/dis.h> +#include <iprt/ctype.h> +#include <iprt/string.h> +#include <iprt/initterm.h> +#include <iprt/param.h> +#include <iprt/path.h> +#include <iprt/thread.h> +#include <iprt/utf16.h> +#include <iprt/zero.h> + +#include "SUPLibInternal.h" +#include "win/SUPHardenedVerify-win.h" +#include "../SUPDrvIOC.h" + +#ifndef IMAGE_SCN_TYPE_NOLOAD +# define IMAGE_SCN_TYPE_NOLOAD 0x00000002 +#endif + + +/********************************************************************************************************************************* +* Defined Constants And Macros * +*********************************************************************************************************************************/ +/** The first argument of a respawed stub when respawned for the first time. + * This just needs to be unique enough to avoid most confusion with real + * executable names, there are other checks in place to make sure we've respanwed. */ +#define SUPR3_RESPAWN_1_ARG0 "60eaff78-4bdd-042d-2e72-669728efd737-suplib-2ndchild" + +/** The first argument of a respawed stub when respawned for the second time. + * This just needs to be unique enough to avoid most confusion with real + * executable names, there are other checks in place to make sure we've respanwed. */ +#define SUPR3_RESPAWN_2_ARG0 "60eaff78-4bdd-042d-2e72-669728efd737-suplib-3rdchild" + +/** Unconditional assertion. */ +#define SUPR3HARDENED_ASSERT(a_Expr) \ + do { \ + if (!(a_Expr)) \ + supR3HardenedFatal("%s: %s\n", __FUNCTION__, #a_Expr); \ + } while (0) + +/** Unconditional assertion of NT_SUCCESS. */ +#define SUPR3HARDENED_ASSERT_NT_SUCCESS(a_Expr) \ + do { \ + NTSTATUS rcNtAssert = (a_Expr); \ + if (!NT_SUCCESS(rcNtAssert)) \ + supR3HardenedFatal("%s: %s -> %#x\n", __FUNCTION__, #a_Expr, rcNtAssert); \ + } while (0) + +/** Unconditional assertion of a WIN32 API returning non-FALSE. */ +#define SUPR3HARDENED_ASSERT_WIN32_SUCCESS(a_Expr) \ + do { \ + BOOL fRcAssert = (a_Expr); \ + if (fRcAssert == FALSE) \ + supR3HardenedFatal("%s: %s -> %#x\n", __FUNCTION__, #a_Expr, RtlGetLastWin32Error()); \ + } while (0) + + +/********************************************************************************************************************************* +* Structures and Typedefs * +*********************************************************************************************************************************/ +/** + * Security descriptor cleanup structure. + */ +typedef struct MYSECURITYCLEANUP +{ + union + { + SID Sid; + uint8_t abPadding[SECURITY_MAX_SID_SIZE]; + } Everyone, Owner, User, Login; + union + { + ACL AclHdr; + uint8_t abPadding[1024]; + } Acl; + PSECURITY_DESCRIPTOR pSecDesc; +} MYSECURITYCLEANUP; +/** Pointer to security cleanup structure. */ +typedef MYSECURITYCLEANUP *PMYSECURITYCLEANUP; + + +/** + * Image verifier cache entry. + */ +typedef struct VERIFIERCACHEENTRY +{ + /** Pointer to the next entry with the same hash value. */ + struct VERIFIERCACHEENTRY * volatile pNext; + /** Next entry in the WinVerifyTrust todo list. */ + struct VERIFIERCACHEENTRY * volatile pNextTodoWvt; + + /** The file handle. */ + HANDLE hFile; + /** If fIndexNumber is set, this is an file system internal file identifier. */ + LARGE_INTEGER IndexNumber; + /** The path hash value. */ + uint32_t uHash; + /** The verification result. */ + int rc; + /** Used for shutting up load and error messages after a while so they don't + * flood the log file and fill up the disk. */ + uint32_t volatile cHits; + /** The validation flags (for WinVerifyTrust retry). */ + uint32_t fFlags; + /** Whether IndexNumber is valid */ + bool fIndexNumberValid; + /** Whether verified by WinVerifyTrust. */ + bool volatile fWinVerifyTrust; + /** cwcPath * sizeof(RTUTF16). */ + uint16_t cbPath; + /** The full path of this entry (variable size). */ + RTUTF16 wszPath[1]; +} VERIFIERCACHEENTRY; +/** Pointer to an image verifier path entry. */ +typedef VERIFIERCACHEENTRY *PVERIFIERCACHEENTRY; + + +/** + * Name of an import DLL that we need to check out. + */ +typedef struct VERIFIERCACHEIMPORT +{ + /** Pointer to the next DLL in the list. */ + struct VERIFIERCACHEIMPORT * volatile pNext; + /** The length of pwszAltSearchDir if available. */ + uint32_t cwcAltSearchDir; + /** This points the directory containing the DLL needing it, this will be + * NULL for a System32 DLL. */ + PWCHAR pwszAltSearchDir; + /** The name of the import DLL (variable length). */ + char szName[1]; +} VERIFIERCACHEIMPORT; +/** Pointer to a import DLL that needs checking out. */ +typedef VERIFIERCACHEIMPORT *PVERIFIERCACHEIMPORT; + + +/** + * Child requests. + */ +typedef enum SUPR3WINCHILDREQ +{ + /** Perform child purification and close full access handles (must be zero). */ + kSupR3WinChildReq_PurifyChildAndCloseHandles = 0, + /** Close the events, we're good on our own from here on. */ + kSupR3WinChildReq_CloseEvents, + /** Reporting error. */ + kSupR3WinChildReq_Error, + /** End of valid requests. */ + kSupR3WinChildReq_End +} SUPR3WINCHILDREQ; + +/** + * Child process parameters. + */ +typedef struct SUPR3WINPROCPARAMS +{ + /** The event semaphore the child will be waiting on. */ + HANDLE hEvtChild; + /** The event semaphore the parent will be waiting on. */ + HANDLE hEvtParent; + + /** The address of the NTDLL. This is only valid during the very early + * initialization as we abuse for thread creation protection. */ + uintptr_t uNtDllAddr; + + /** The requested operation (set by the child). */ + SUPR3WINCHILDREQ enmRequest; + /** The last status. */ + int32_t rc; + /** The init operation the error relates to if message, kSupInitOp_Invalid if + * not message. */ + SUPINITOP enmWhat; + /** Where if message. */ + char szWhere[80]; + /** Error message / path name string space. */ + char szErrorMsg[16384+1024]; +} SUPR3WINPROCPARAMS; + + +/** + * Child process data structure for use during child process init setup and + * purification. + */ +typedef struct SUPR3HARDNTCHILD +{ + /** Process handle. */ + HANDLE hProcess; + /** Primary thread handle. */ + HANDLE hThread; + /** Handle to the parent process, if we're the middle (stub) process. */ + HANDLE hParent; + /** The event semaphore the child will be waiting on. */ + HANDLE hEvtChild; + /** The event semaphore the parent will be waiting on. */ + HANDLE hEvtParent; + /** The address of NTDLL in the child. */ + uintptr_t uNtDllAddr; + /** The address of NTDLL in this process. */ + uintptr_t uNtDllParentAddr; + /** Which respawn number this is (1 = stub, 2 = VM). */ + int iWhich; + /** The basic process info. */ + PROCESS_BASIC_INFORMATION BasicInfo; + /** The probable size of the PEB. */ + size_t cbPeb; + /** The pristine process environment block. */ + PEB Peb; + /** The child process parameters. */ + SUPR3WINPROCPARAMS ProcParams; +} SUPR3HARDNTCHILD; +/** Pointer to a child process data structure. */ +typedef SUPR3HARDNTCHILD *PSUPR3HARDNTCHILD; + + +/********************************************************************************************************************************* +* Global Variables * +*********************************************************************************************************************************/ +/** Process parameters. Specified by parent if VM process, see + * supR3HardenedVmProcessInit. */ +static SUPR3WINPROCPARAMS g_ProcParams = { NULL, NULL, 0, (SUPR3WINCHILDREQ)0, 0 }; +/** Set if supR3HardenedEarlyProcessInit was invoked. */ +bool g_fSupEarlyProcessInit = false; +/** Set if the stub device has been opened (stub process only). */ +bool g_fSupStubOpened = false; + +/** @name Global variables initialized by suplibHardenedWindowsMain. + * @{ */ +/** Combined windows NT version number. See SUP_MAKE_NT_VER_COMBINED. */ +uint32_t g_uNtVerCombined = 0; +/** Count calls to the special main function for linking santity checks. */ +static uint32_t volatile g_cSuplibHardenedWindowsMainCalls; +/** The UTF-16 windows path to the executable. */ +RTUTF16 g_wszSupLibHardenedExePath[1024]; +/** The NT path of the executable. */ +SUPSYSROOTDIRBUF g_SupLibHardenedExeNtPath; +/** The NT path of the application binary directory. */ +SUPSYSROOTDIRBUF g_SupLibHardenedAppBinNtPath; +/** The offset into g_SupLibHardenedExeNtPath of the executable name (WCHAR, + * not byte). This also gives the length of the exectuable directory path, + * including a trailing slash. */ +static uint32_t g_offSupLibHardenedExeNtName; +/** Set if we need to use the LOAD_LIBRARY_SEARCH_USER_DIRS option. */ +bool g_fSupLibHardenedDllSearchUserDirs = false; +/** @} */ + +/** @name Hook related variables. + * @{ */ +/** Pointer to the bit of assembly code that will perform the original + * NtCreateSection operation. */ +static NTSTATUS (NTAPI *g_pfnNtCreateSectionReal)(PHANDLE, ACCESS_MASK, POBJECT_ATTRIBUTES, + PLARGE_INTEGER, ULONG, ULONG, HANDLE); +/** Pointer to the NtCreateSection function in NtDll (for patching purposes). */ +static uint8_t *g_pbNtCreateSection; +/** The patched NtCreateSection bytes (for restoring). */ +static uint8_t g_abNtCreateSectionPatch[16]; +/** Pointer to the bit of assembly code that will perform the original + * LdrLoadDll operation. */ +static NTSTATUS (NTAPI *g_pfnLdrLoadDllReal)(PWSTR, PULONG, PUNICODE_STRING, PHANDLE); +/** Pointer to the LdrLoadDll function in NtDll (for patching purposes). */ +static uint8_t *g_pbLdrLoadDll; +/** The patched LdrLoadDll bytes (for restoring). */ +static uint8_t g_abLdrLoadDllPatch[16]; + +#ifndef VBOX_WITHOUT_HARDENDED_XCPT_LOGGING +/** Pointer to the bit of assembly code that will perform the original + * KiUserExceptionDispatcher operation. */ +static VOID (NTAPI *g_pfnKiUserExceptionDispatcherReal)(void); +/** Pointer to the KiUserExceptionDispatcher function in NtDll (for patching purposes). */ +static uint8_t *g_pbKiUserExceptionDispatcher; +/** The patched KiUserExceptionDispatcher bytes (for restoring). */ +static uint8_t g_abKiUserExceptionDispatcherPatch[16]; +#endif + +/** Pointer to the bit of assembly code that will perform the original + * KiUserApcDispatcher operation. */ +static VOID (NTAPI *g_pfnKiUserApcDispatcherReal)(void); +/** Pointer to the KiUserApcDispatcher function in NtDll (for patching purposes). */ +static uint8_t *g_pbKiUserApcDispatcher; +/** The patched KiUserApcDispatcher bytes (for restoring). */ +static uint8_t g_abKiUserApcDispatcherPatch[16]; + +/** Pointer to the LdrInitializeThunk function in NtDll for + * supR3HardenedMonitor_KiUserApcDispatcher_C() to use for APC vetting. */ +static uintptr_t g_pfnLdrInitializeThunk; + +/** The hash table of verifier cache . */ +static PVERIFIERCACHEENTRY volatile g_apVerifierCache[128]; +/** Queue of cached images which needs WinVerifyTrust to check them. */ +static PVERIFIERCACHEENTRY volatile g_pVerifierCacheTodoWvt = NULL; +/** Queue of cached images which needs their imports checked. */ +static PVERIFIERCACHEIMPORT volatile g_pVerifierCacheTodoImports = NULL; + +/** The windows path to dir \\SystemRoot\\System32 directory (technically + * this whatever \\KnownDlls\\KnownDllPath points to). */ +SUPSYSROOTDIRBUF g_System32WinPath; +/** @} */ + +/** Positive if the DLL notification callback has been registered, counts + * registration attempts as negative. */ +static int g_cDllNotificationRegistered = 0; +/** The registration cookie of the DLL notification callback. */ +static PVOID g_pvDllNotificationCookie = NULL; + +/** Static error info structure used during init. */ +static RTERRINFOSTATIC g_ErrInfoStatic; + +/** In the assembly file. */ +extern "C" uint8_t g_abSupHardReadWriteExecPage[PAGE_SIZE]; + +/** Whether we've patched our own LdrInitializeThunk or not. We do this to + * disable thread creation. */ +static bool g_fSupInitThunkSelfPatched; +/** The backup of our own LdrInitializeThunk code, for enabling and disabling + * thread creation in this process. */ +static uint8_t g_abLdrInitThunkSelfBackup[16]; + +/** Mask of adversaries that we've detected (SUPHARDNT_ADVERSARY_XXX). */ +static uint32_t g_fSupAdversaries = 0; +/** @name SUPHARDNT_ADVERSARY_XXX - Adversaries + * @{ */ +/** Symantec endpoint protection or similar including SysPlant.sys. */ +#define SUPHARDNT_ADVERSARY_SYMANTEC_SYSPLANT RT_BIT_32(0) +/** Symantec Norton 360. */ +#define SUPHARDNT_ADVERSARY_SYMANTEC_N360 RT_BIT_32(1) +/** Avast! */ +#define SUPHARDNT_ADVERSARY_AVAST RT_BIT_32(2) +/** TrendMicro OfficeScan and probably others. */ +#define SUPHARDNT_ADVERSARY_TRENDMICRO RT_BIT_32(3) +/** TrendMicro potentially buggy sakfile.sys. */ +#define SUPHARDNT_ADVERSARY_TRENDMICRO_SAKFILE RT_BIT_32(4) +/** McAfee. */ +#define SUPHARDNT_ADVERSARY_MCAFEE RT_BIT_32(5) +/** Kaspersky or OEMs of it. */ +#define SUPHARDNT_ADVERSARY_KASPERSKY RT_BIT_32(6) +/** Malwarebytes Anti-Malware (MBAM). */ +#define SUPHARDNT_ADVERSARY_MBAM RT_BIT_32(7) +/** AVG Internet Security. */ +#define SUPHARDNT_ADVERSARY_AVG RT_BIT_32(8) +/** Panda Security. */ +#define SUPHARDNT_ADVERSARY_PANDA RT_BIT_32(9) +/** Microsoft Security Essentials. */ +#define SUPHARDNT_ADVERSARY_MSE RT_BIT_32(10) +/** Comodo. */ +#define SUPHARDNT_ADVERSARY_COMODO RT_BIT_32(11) +/** Check Point's Zone Alarm (may include Kaspersky). */ +#define SUPHARDNT_ADVERSARY_ZONE_ALARM RT_BIT_32(12) +/** Digital guardian, old problematic version. */ +#define SUPHARDNT_ADVERSARY_DIGITAL_GUARDIAN_OLD RT_BIT_32(13) +/** Digital guardian, new version. */ +#define SUPHARDNT_ADVERSARY_DIGITAL_GUARDIAN_NEW RT_BIT_32(14) +/** Cylance protect or something (from googling, no available sample copy). */ +#define SUPHARDNT_ADVERSARY_CYLANCE RT_BIT_32(15) +/** BeyondTrust / PowerBroker / something (googling, no available sample copy). */ +#define SUPHARDNT_ADVERSARY_BEYONDTRUST RT_BIT_32(16) +/** Avecto / Defendpoint / Privilege Guard (details from support guy, hoping to get sample copy). */ +#define SUPHARDNT_ADVERSARY_AVECTO RT_BIT_32(17) +/** Sophos Endpoint Defense. */ +#define SUPHARDNT_ADVERSARY_SOPHOS RT_BIT_32(18) +/** VMware horizon view agent. */ +#define SUPHARDNT_ADVERSARY_HORIZON_VIEW_AGENT RT_BIT_32(19) +/** Unknown adversary detected while waiting on child. */ +#define SUPHARDNT_ADVERSARY_UNKNOWN RT_BIT_32(31) +/** @} */ + + +/********************************************************************************************************************************* +* Internal Functions * +*********************************************************************************************************************************/ +static NTSTATUS supR3HardenedScreenImage(HANDLE hFile, bool fImage, bool fIgnoreArch, PULONG pfAccess, PULONG pfProtect, + bool *pfCallRealApi, const char *pszCaller, bool fAvoidWinVerifyTrust, + bool *pfQuiet) RT_NOTHROW_PROTO; +static void supR3HardenedWinRegisterDllNotificationCallback(void); +static void supR3HardenedWinReInstallHooks(bool fFirst) RT_NOTHROW_PROTO; +DECLASM(void) supR3HardenedEarlyProcessInitThunk(void); +DECLASM(void) supR3HardenedMonitor_KiUserApcDispatcher(void); +#ifndef VBOX_WITHOUT_HARDENDED_XCPT_LOGGING +DECLASM(void) supR3HardenedMonitor_KiUserExceptionDispatcher(void); +#endif +extern "C" void __stdcall suplibHardenedWindowsMain(void); + + +#if 0 /* unused */ + +/** + * Simple wide char search routine. + * + * @returns Pointer to the first location of @a wcNeedle in @a pwszHaystack. + * NULL if not found. + * @param pwszHaystack Pointer to the string that should be searched. + * @param wcNeedle The character to search for. + */ +static PRTUTF16 suplibHardenedWStrChr(PCRTUTF16 pwszHaystack, RTUTF16 wcNeedle) +{ + for (;;) + { + RTUTF16 wcCur = *pwszHaystack; + if (wcCur == wcNeedle) + return (PRTUTF16)pwszHaystack; + if (wcCur == '\0') + return NULL; + pwszHaystack++; + } +} + + +/** + * Simple wide char string length routine. + * + * @returns The number of characters in the given string. (Excludes the + * terminator.) + * @param pwsz The string. + */ +static size_t suplibHardenedWStrLen(PCRTUTF16 pwsz) +{ + PCRTUTF16 pwszCur = pwsz; + while (*pwszCur != '\0') + pwszCur++; + return pwszCur - pwsz; +} + +#endif /* unused */ + + +/** + * Our version of GetTickCount. + * @returns Millisecond timestamp. + */ +static uint64_t supR3HardenedWinGetMilliTS(void) +{ + PKUSER_SHARED_DATA pUserSharedData = (PKUSER_SHARED_DATA)(uintptr_t)0x7ffe0000; + + /* use interrupt time */ + LARGE_INTEGER Time; + do + { + Time.HighPart = pUserSharedData->InterruptTime.High1Time; + Time.LowPart = pUserSharedData->InterruptTime.LowPart; + } while (pUserSharedData->InterruptTime.High2Time != Time.HighPart); + + return (uint64_t)Time.QuadPart / 10000; +} + + +/** + * Called when there is some /GS (or maybe /RTCsu) related stack problem. + * + * We don't want the CRT version living in gshandle.obj, as it uses a lot of + * kernel32 imports, we want to report this error ourselves. + */ +extern "C" __declspec(noreturn guard(nosspro) guard(nossepi)) +void __cdecl __report_rangecheckfailure(void) +{ + supR3HardenedFatal("__report_rangecheckfailure called from %p", ASMReturnAddress()); +} + + +/** + * Called when there is some /GS problem has been detected. + * + * We don't want the CRT version living in gshandle.obj, as it uses a lot of + * kernel32 imports, we want to report this error ourselves. + */ +extern "C" __declspec(noreturn guard(nosspro) guard(nossepi)) +#ifdef RT_ARCH_X86 +void __cdecl __report_gsfailure(void) +#else +void __report_gsfailure(uintptr_t uCookie) +#endif +{ +#ifdef RT_ARCH_X86 + supR3HardenedFatal("__report_gsfailure called from %p", ASMReturnAddress()); +#else + supR3HardenedFatal("__report_gsfailure called from %p, cookie=%p", ASMReturnAddress(), uCookie); +#endif +} + + +/** + * Wrapper around LoadLibraryEx that deals with the UTF-8 to UTF-16 conversion + * and supplies the right flags. + * + * @returns Module handle on success, NULL on failure. + * @param pszName The full path to the DLL. + * @param fSystem32Only Whether to only look for imports in the system32 + * directory. If set to false, the application + * directory is also searched. + * @param fMainFlags The main flags (giving the location), if the DLL + * being loaded is loaded from the app bin + * directory and import other DLLs from there. Pass + * 0 (= SUPSECMAIN_FLAGS_LOC_APP_BIN) if not + * applicable. Ignored if @a fSystem32Only is set. + * + * This is only needed to load VBoxRT.dll when + * executing a testcase from the testcase/ subdir. + */ +DECLHIDDEN(void *) supR3HardenedWinLoadLibrary(const char *pszName, bool fSystem32Only, uint32_t fMainFlags) +{ + WCHAR wszPath[RTPATH_MAX]; + PRTUTF16 pwszPath = wszPath; + int rc = RTStrToUtf16Ex(pszName, RTSTR_MAX, &pwszPath, RT_ELEMENTS(wszPath), NULL); + if (RT_SUCCESS(rc)) + { + while (*pwszPath) + { + if (*pwszPath == '/') + *pwszPath = '\\'; + pwszPath++; + } + + DWORD fFlags = 0; + if (g_uNtVerCombined >= SUP_MAKE_NT_VER_SIMPLE(6, 0)) + { + fFlags |= LOAD_LIBRARY_SEARCH_SYSTEM32; + if (!fSystem32Only) + { + fFlags |= LOAD_LIBRARY_SEARCH_APPLICATION_DIR; + if (g_fSupLibHardenedDllSearchUserDirs) + fFlags |= LOAD_LIBRARY_SEARCH_USER_DIRS; + if ((fMainFlags & SUPSECMAIN_FLAGS_LOC_MASK) != SUPSECMAIN_FLAGS_LOC_APP_BIN) + fFlags |= LOAD_LIBRARY_SEARCH_DLL_LOAD_DIR; + } + } + + void *pvRet = (void *)LoadLibraryExW(wszPath, NULL /*hFile*/, fFlags); + + /* Vista, W7, W2K8R might not work without KB2533623, so retry with no flags. */ + if ( !pvRet + && fFlags + && g_uNtVerCombined < SUP_MAKE_NT_VER_SIMPLE(6, 2) + && RtlGetLastWin32Error() == ERROR_INVALID_PARAMETER) + pvRet = (void *)LoadLibraryExW(wszPath, NULL /*hFile*/, 0); + + return pvRet; + } + supR3HardenedFatal("RTStrToUtf16Ex failed on '%s': %Rrc", pszName, rc); + /* not reached */ +} + + +/** + * Gets the internal index number of the file. + * + * @returns True if we got an index number, false if not. + * @param hFile The file in question. + * @param pIndexNumber where to return the index number. + */ +static bool supR3HardenedWinVerifyCacheGetIndexNumber(HANDLE hFile, PLARGE_INTEGER pIndexNumber) RT_NOTHROW_DEF +{ + IO_STATUS_BLOCK Ios = RTNT_IO_STATUS_BLOCK_INITIALIZER; + NTSTATUS rcNt = NtQueryInformationFile(hFile, &Ios, pIndexNumber, sizeof(*pIndexNumber), FileInternalInformation); + if (NT_SUCCESS(rcNt)) + rcNt = Ios.Status; +#ifdef DEBUG_bird + if (!NT_SUCCESS(rcNt)) + __debugbreak(); +#endif + return NT_SUCCESS(rcNt) && pIndexNumber->QuadPart != 0; +} + + +/** + * Calculates the hash value for the given UTF-16 path string. + * + * @returns Hash value. + * @param pUniStr String to hash. + */ +static uint32_t supR3HardenedWinVerifyCacheHashPath(PCUNICODE_STRING pUniStr) RT_NOTHROW_DEF +{ + uint32_t uHash = 0; + unsigned cwcLeft = pUniStr->Length / sizeof(WCHAR); + PRTUTF16 pwc = pUniStr->Buffer; + + while (cwcLeft-- > 0) + { + RTUTF16 wc = *pwc++; + if (wc < 0x80) + wc = wc != '/' ? RT_C_TO_LOWER(wc) : '\\'; + uHash = wc + (uHash << 6) + (uHash << 16) - uHash; + } + return uHash; +} + + +/** + * Calculates the hash value for a directory + filename combo as if they were + * one single string. + * + * @returns Hash value. + * @param pawcDir The directory name. + * @param cwcDir The length of the directory name. RTSTR_MAX if + * not available. + * @param pszName The import name (UTF-8). + */ +static uint32_t supR3HardenedWinVerifyCacheHashDirAndFile(PCRTUTF16 pawcDir, uint32_t cwcDir, const char *pszName) RT_NOTHROW_DEF +{ + uint32_t uHash = 0; + while (cwcDir-- > 0) + { + RTUTF16 wc = *pawcDir++; + if (wc < 0x80) + wc = wc != '/' ? RT_C_TO_LOWER(wc) : '\\'; + uHash = wc + (uHash << 6) + (uHash << 16) - uHash; + } + + unsigned char ch = '\\'; + uHash = ch + (uHash << 6) + (uHash << 16) - uHash; + + while ((ch = *pszName++) != '\0') + { + ch = RT_C_TO_LOWER(ch); + uHash = ch + (uHash << 6) + (uHash << 16) - uHash; + } + + return uHash; +} + + +/** + * Verify string cache compare function. + * + * @returns true if the strings match, false if not. + * @param pawcLeft The left hand string. + * @param pawcRight The right hand string. + * @param cwcToCompare The number of chars to compare. + */ +static bool supR3HardenedWinVerifyCacheIsMatch(PCRTUTF16 pawcLeft, PCRTUTF16 pawcRight, uint32_t cwcToCompare) RT_NOTHROW_DEF +{ + /* Try a quick memory compare first. */ + if (memcmp(pawcLeft, pawcRight, cwcToCompare * sizeof(RTUTF16)) == 0) + return true; + + /* Slow char by char compare. */ + while (cwcToCompare-- > 0) + { + RTUTF16 wcLeft = *pawcLeft++; + RTUTF16 wcRight = *pawcRight++; + if (wcLeft != wcRight) + { + wcLeft = wcLeft != '/' ? RT_C_TO_LOWER(wcLeft) : '\\'; + wcRight = wcRight != '/' ? RT_C_TO_LOWER(wcRight) : '\\'; + if (wcLeft != wcRight) + return false; + } + } + + return true; +} + + + +/** + * Inserts the given verifier result into the cache. + * + * @param pUniStr The full path of the image. + * @param hFile The file handle - must either be entered into + * the cache or closed. + * @param rc The verifier result. + * @param fWinVerifyTrust Whether verified by WinVerifyTrust or not. + * @param fFlags The image verification flags. + */ +static void supR3HardenedWinVerifyCacheInsert(PCUNICODE_STRING pUniStr, HANDLE hFile, int rc, + bool fWinVerifyTrust, uint32_t fFlags) RT_NOTHROW_DEF +{ + /* + * Allocate and initalize a new entry. + */ + PVERIFIERCACHEENTRY pEntry = (PVERIFIERCACHEENTRY)RTMemAllocZ(sizeof(VERIFIERCACHEENTRY) + pUniStr->Length); + if (pEntry) + { + pEntry->pNext = NULL; + pEntry->pNextTodoWvt = NULL; + pEntry->hFile = hFile; + pEntry->uHash = supR3HardenedWinVerifyCacheHashPath(pUniStr); + pEntry->rc = rc; + pEntry->fFlags = fFlags; + pEntry->cHits = 0; + pEntry->fWinVerifyTrust = fWinVerifyTrust; + pEntry->cbPath = pUniStr->Length; + memcpy(pEntry->wszPath, pUniStr->Buffer, pUniStr->Length); + pEntry->wszPath[pUniStr->Length / sizeof(WCHAR)] = '\0'; + pEntry->fIndexNumberValid = supR3HardenedWinVerifyCacheGetIndexNumber(hFile, &pEntry->IndexNumber); + + /* + * Try insert it, careful with concurrent code as well as potential duplicates. + */ + uint32_t iHashTab = pEntry->uHash % RT_ELEMENTS(g_apVerifierCache); + VERIFIERCACHEENTRY * volatile *ppEntry = &g_apVerifierCache[iHashTab]; + for (;;) + { + if (ASMAtomicCmpXchgPtr(ppEntry, pEntry, NULL)) + { + if (!fWinVerifyTrust) + do + pEntry->pNextTodoWvt = g_pVerifierCacheTodoWvt; + while (!ASMAtomicCmpXchgPtr(&g_pVerifierCacheTodoWvt, pEntry, pEntry->pNextTodoWvt)); + + SUP_DPRINTF(("supR3HardenedWinVerifyCacheInsert: %ls\n", pUniStr->Buffer)); + return; + } + + PVERIFIERCACHEENTRY pOther = *ppEntry; + if (!pOther) + continue; + if ( pOther->uHash == pEntry->uHash + && pOther->cbPath == pEntry->cbPath + && supR3HardenedWinVerifyCacheIsMatch(pOther->wszPath, pEntry->wszPath, pEntry->cbPath / sizeof(RTUTF16))) + break; + ppEntry = &pOther->pNext; + } + + /* Duplicate entry (may happen due to races). */ + RTMemFree(pEntry); + } + NtClose(hFile); +} + + +/** + * Looks up an entry in the verifier hash table. + * + * @return Pointer to the entry on if found, NULL if not. + * @param pUniStr The full path of the image. + * @param hFile The file handle. + */ +static PVERIFIERCACHEENTRY supR3HardenedWinVerifyCacheLookup(PCUNICODE_STRING pUniStr, HANDLE hFile) RT_NOTHROW_DEF +{ + PRTUTF16 const pwszPath = pUniStr->Buffer; + uint16_t const cbPath = pUniStr->Length; + uint32_t uHash = supR3HardenedWinVerifyCacheHashPath(pUniStr); + uint32_t iHashTab = uHash % RT_ELEMENTS(g_apVerifierCache); + PVERIFIERCACHEENTRY pCur = g_apVerifierCache[iHashTab]; + while (pCur) + { + if ( pCur->uHash == uHash + && pCur->cbPath == cbPath + && supR3HardenedWinVerifyCacheIsMatch(pCur->wszPath, pwszPath, cbPath / sizeof(RTUTF16))) + { + + if (!pCur->fIndexNumberValid) + return pCur; + LARGE_INTEGER IndexNumber; + bool fIndexNumberValid = supR3HardenedWinVerifyCacheGetIndexNumber(hFile, &IndexNumber); + if ( fIndexNumberValid + && IndexNumber.QuadPart == pCur->IndexNumber.QuadPart) + return pCur; +#ifdef DEBUG_bird + __debugbreak(); +#endif + } + pCur = pCur->pNext; + } + return NULL; +} + + +/** + * Looks up an import DLL in the verifier hash table. + * + * @return Pointer to the entry on if found, NULL if not. + * @param pawcDir The directory name. + * @param cwcDir The length of the directory name. + * @param pszName The import name (UTF-8). + */ +static PVERIFIERCACHEENTRY supR3HardenedWinVerifyCacheLookupImport(PCRTUTF16 pawcDir, uint32_t cwcDir, const char *pszName) +{ + uint32_t uHash = supR3HardenedWinVerifyCacheHashDirAndFile(pawcDir, cwcDir, pszName); + uint32_t iHashTab = uHash % RT_ELEMENTS(g_apVerifierCache); + uint32_t const cbPath = (uint32_t)((cwcDir + 1 + strlen(pszName)) * sizeof(RTUTF16)); + PVERIFIERCACHEENTRY pCur = g_apVerifierCache[iHashTab]; + while (pCur) + { + if ( pCur->uHash == uHash + && pCur->cbPath == cbPath) + { + if (supR3HardenedWinVerifyCacheIsMatch(pCur->wszPath, pawcDir, cwcDir)) + { + if (pCur->wszPath[cwcDir] == '\\' || pCur->wszPath[cwcDir] == '/') + { + if (RTUtf16ICmpAscii(&pCur->wszPath[cwcDir + 1], pszName)) + { + return pCur; + } + } + } + } + + pCur = pCur->pNext; + } + return NULL; +} + + +/** + * Schedules the import DLLs for verification and entry into the cache. + * + * @param hLdrMod The loader module which imports should be + * scheduled for verification. + * @param pwszName The full NT path of the module. + */ +DECLHIDDEN(void) supR3HardenedWinVerifyCacheScheduleImports(RTLDRMOD hLdrMod, PCRTUTF16 pwszName) +{ + /* + * Any imports? + */ + uint32_t cImports; + int rc = RTLdrQueryPropEx(hLdrMod, RTLDRPROP_IMPORT_COUNT, NULL /*pvBits*/, &cImports, sizeof(cImports), NULL); + if (RT_SUCCESS(rc)) + { + if (cImports) + { + /* + * Figure out the DLL directory from pwszName. + */ + PCRTUTF16 pawcDir = pwszName; + uint32_t cwcDir = 0; + uint32_t i = 0; + RTUTF16 wc; + while ((wc = pawcDir[i++]) != '\0') + if ((wc == '\\' || wc == '/' || wc == ':') && cwcDir + 2 != i) + cwcDir = i - 1; + if ( g_System32NtPath.UniStr.Length / sizeof(WCHAR) == cwcDir + && supR3HardenedWinVerifyCacheIsMatch(pawcDir, g_System32NtPath.UniStr.Buffer, cwcDir)) + pawcDir = NULL; + + /* + * Enumerate the imports. + */ + for (i = 0; i < cImports; i++) + { + union + { + char szName[256]; + uint32_t iImport; + } uBuf; + uBuf.iImport = i; + rc = RTLdrQueryPropEx(hLdrMod, RTLDRPROP_IMPORT_MODULE, NULL /*pvBits*/, &uBuf, sizeof(uBuf), NULL); + if (RT_SUCCESS(rc)) + { + /* + * Skip kernel32, ntdll and API set stuff. + */ + RTStrToLower(uBuf.szName); + if ( RTStrCmp(uBuf.szName, "kernel32.dll") == 0 + || RTStrCmp(uBuf.szName, "kernelbase.dll") == 0 + || RTStrCmp(uBuf.szName, "ntdll.dll") == 0 + || RTStrNCmp(uBuf.szName, RT_STR_TUPLE("api-ms-win-")) == 0 + || RTStrNCmp(uBuf.szName, RT_STR_TUPLE("ext-ms-win-")) == 0 + ) + { + continue; + } + + /* + * Skip to the next one if it's already in the cache. + */ + if (supR3HardenedWinVerifyCacheLookupImport(g_System32NtPath.UniStr.Buffer, + g_System32NtPath.UniStr.Length / sizeof(WCHAR), + uBuf.szName) != NULL) + { + SUP_DPRINTF(("supR3HardenedWinVerifyCacheScheduleImports: '%s' cached for system32\n", uBuf.szName)); + continue; + } + if (supR3HardenedWinVerifyCacheLookupImport(g_SupLibHardenedAppBinNtPath.UniStr.Buffer, + g_SupLibHardenedAppBinNtPath.UniStr.Length / sizeof(CHAR), + uBuf.szName) != NULL) + { + SUP_DPRINTF(("supR3HardenedWinVerifyCacheScheduleImports: '%s' cached for appdir\n", uBuf.szName)); + continue; + } + if (pawcDir && supR3HardenedWinVerifyCacheLookupImport(pawcDir, cwcDir, uBuf.szName) != NULL) + { + SUP_DPRINTF(("supR3HardenedWinVerifyCacheScheduleImports: '%s' cached for dll dir\n", uBuf.szName)); + continue; + } + + /* We could skip already scheduled modules, but that'll require serialization and extra work... */ + + /* + * Add it to the todo list. + */ + SUP_DPRINTF(("supR3HardenedWinVerifyCacheScheduleImports: Import todo: #%u '%s'.\n", i, uBuf.szName)); + uint32_t cbName = (uint32_t)strlen(uBuf.szName) + 1; + uint32_t cbNameAligned = RT_ALIGN_32(cbName, sizeof(RTUTF16)); + uint32_t cbNeeded = RT_UOFFSETOF_DYN(VERIFIERCACHEIMPORT, szName[cbNameAligned]) + + (pawcDir ? (cwcDir + 1) * sizeof(RTUTF16) : 0); + PVERIFIERCACHEIMPORT pImport = (PVERIFIERCACHEIMPORT)RTMemAllocZ(cbNeeded); + if (pImport) + { + /* Init it. */ + memcpy(pImport->szName, uBuf.szName, cbName); + if (!pawcDir) + { + pImport->cwcAltSearchDir = 0; + pImport->pwszAltSearchDir = NULL; + } + else + { + pImport->cwcAltSearchDir = cwcDir; + pImport->pwszAltSearchDir = (PRTUTF16)&pImport->szName[cbNameAligned]; + memcpy(pImport->pwszAltSearchDir, pawcDir, cwcDir * sizeof(RTUTF16)); + pImport->pwszAltSearchDir[cwcDir] = '\0'; + } + + /* Insert it. */ + do + pImport->pNext = g_pVerifierCacheTodoImports; + while (!ASMAtomicCmpXchgPtr(&g_pVerifierCacheTodoImports, pImport, pImport->pNext)); + } + } + else + SUP_DPRINTF(("RTLDRPROP_IMPORT_MODULE failed with rc=%Rrc i=%#x on '%ls'\n", rc, i, pwszName)); + } + } + else + SUP_DPRINTF(("'%ls' has no imports\n", pwszName)); + } + else + SUP_DPRINTF(("RTLDRPROP_IMPORT_COUNT failed with rc=%Rrc on '%ls'\n", rc, pwszName)); +} + + +/** + * Processes the list of import todos. + */ +static void supR3HardenedWinVerifyCacheProcessImportTodos(void) +{ + /* + * Work until we've got nothing more todo. + */ + for (;;) + { + PVERIFIERCACHEIMPORT pTodo = ASMAtomicXchgPtrT(&g_pVerifierCacheTodoImports, NULL, PVERIFIERCACHEIMPORT); + if (!pTodo) + break; + do + { + PVERIFIERCACHEIMPORT pCur = pTodo; + pTodo = pTodo->pNext; + + /* + * Not in the cached already? + */ + if ( !supR3HardenedWinVerifyCacheLookupImport(g_System32NtPath.UniStr.Buffer, + g_System32NtPath.UniStr.Length / sizeof(WCHAR), + pCur->szName) + && !supR3HardenedWinVerifyCacheLookupImport(g_SupLibHardenedAppBinNtPath.UniStr.Buffer, + g_SupLibHardenedAppBinNtPath.UniStr.Length / sizeof(WCHAR), + pCur->szName) + && ( pCur->cwcAltSearchDir == 0 + || !supR3HardenedWinVerifyCacheLookupImport(pCur->pwszAltSearchDir, pCur->cwcAltSearchDir, pCur->szName)) ) + { + /* + * Try locate the imported DLL and open it. + */ + SUP_DPRINTF(("supR3HardenedWinVerifyCacheProcessImportTodos: Processing '%s'...\n", pCur->szName)); + + NTSTATUS rcNt; + NTSTATUS rcNtRedir = 0x22222222; + HANDLE hFile = INVALID_HANDLE_VALUE; + RTUTF16 wszPath[260 + 260]; /* Assumes we've limited the import name length to 256. */ + AssertCompile(sizeof(wszPath) > sizeof(g_System32NtPath)); + + /* + * Check for DLL isolation / redirection / mapping. + */ + size_t cwcName = 260; + PRTUTF16 pwszName = &wszPath[0]; + int rc = RTStrToUtf16Ex(pCur->szName, RTSTR_MAX, &pwszName, cwcName, &cwcName); + if (RT_SUCCESS(rc)) + { + UNICODE_STRING UniStrName; + UniStrName.Buffer = wszPath; + UniStrName.Length = (USHORT)cwcName * sizeof(WCHAR); + UniStrName.MaximumLength = UniStrName.Length + sizeof(WCHAR); + + UNICODE_STRING UniStrStatic; + UniStrStatic.Buffer = &wszPath[cwcName + 1]; + UniStrStatic.Length = 0; + UniStrStatic.MaximumLength = (USHORT)(sizeof(wszPath) - cwcName * sizeof(WCHAR) - sizeof(WCHAR)); + + static UNICODE_STRING const s_DefaultSuffix = RTNT_CONSTANT_UNISTR(L".dll"); + UNICODE_STRING UniStrDynamic = { 0, 0, NULL }; + PUNICODE_STRING pUniStrResult = NULL; + + rcNtRedir = RtlDosApplyFileIsolationRedirection_Ustr(1 /*fFlags*/, + &UniStrName, + (PUNICODE_STRING)&s_DefaultSuffix, + &UniStrStatic, + &UniStrDynamic, + &pUniStrResult, + NULL /*pNewFlags*/, + NULL /*pcbFilename*/, + NULL /*pcbNeeded*/); + if (NT_SUCCESS(rcNtRedir)) + { + IO_STATUS_BLOCK Ios = RTNT_IO_STATUS_BLOCK_INITIALIZER; + OBJECT_ATTRIBUTES ObjAttr; + InitializeObjectAttributes(&ObjAttr, pUniStrResult, + OBJ_CASE_INSENSITIVE, NULL /*hRootDir*/, NULL /*pSecDesc*/); + rcNt = NtCreateFile(&hFile, + FILE_READ_DATA | READ_CONTROL | 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)) + { + /* For accurate logging. */ + size_t cwcCopy = RT_MIN(pUniStrResult->Length / sizeof(RTUTF16), RT_ELEMENTS(wszPath) - 1); + memcpy(wszPath, pUniStrResult->Buffer, cwcCopy * sizeof(RTUTF16)); + wszPath[cwcCopy] = '\0'; + } + else + hFile = INVALID_HANDLE_VALUE; + RtlFreeUnicodeString(&UniStrDynamic); + } + } + else + SUP_DPRINTF(("supR3HardenedWinVerifyCacheProcessImportTodos: RTStrToUtf16Ex #1 failed: %Rrc\n", rc)); + + /* + * If not something that gets remapped, do the half normal searching we need. + */ + if (hFile == INVALID_HANDLE_VALUE) + { + struct + { + PRTUTF16 pawcDir; + uint32_t cwcDir; + } Tmp, aDirs[] = + { + { g_System32NtPath.UniStr.Buffer, g_System32NtPath.UniStr.Length / sizeof(WCHAR) }, + { g_SupLibHardenedExeNtPath.UniStr.Buffer, g_SupLibHardenedAppBinNtPath.UniStr.Length / sizeof(WCHAR) }, + { pCur->pwszAltSearchDir, pCur->cwcAltSearchDir }, + }; + + /* Search System32 first, unless it's a 'V*' or 'm*' name, the latter for msvcrt. */ + if ( pCur->szName[0] == 'v' + || pCur->szName[0] == 'V' + || pCur->szName[0] == 'm' + || pCur->szName[0] == 'M') + { + Tmp = aDirs[0]; + aDirs[0] = aDirs[1]; + aDirs[1] = Tmp; + } + + for (uint32_t i = 0; i < RT_ELEMENTS(aDirs); i++) + { + if (aDirs[i].pawcDir && aDirs[i].cwcDir && aDirs[i].cwcDir < RT_ELEMENTS(wszPath) / 3 * 2) + { + memcpy(wszPath, aDirs[i].pawcDir, aDirs[i].cwcDir * sizeof(RTUTF16)); + uint32_t cwc = aDirs[i].cwcDir; + wszPath[cwc++] = '\\'; + cwcName = RT_ELEMENTS(wszPath) - cwc; + pwszName = &wszPath[cwc]; + rc = RTStrToUtf16Ex(pCur->szName, RTSTR_MAX, &pwszName, cwcName, &cwcName); + if (RT_SUCCESS(rc)) + { + IO_STATUS_BLOCK Ios = RTNT_IO_STATUS_BLOCK_INITIALIZER; + UNICODE_STRING NtName; + NtName.Buffer = wszPath; + NtName.Length = (USHORT)((cwc + cwcName) * sizeof(WCHAR)); + NtName.MaximumLength = NtName.Length + sizeof(WCHAR); + OBJECT_ATTRIBUTES ObjAttr; + InitializeObjectAttributes(&ObjAttr, &NtName, OBJ_CASE_INSENSITIVE, NULL /*hRootDir*/, NULL /*pSecDesc*/); + + rcNt = NtCreateFile(&hFile, + FILE_READ_DATA | READ_CONTROL | 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)) + break; + hFile = INVALID_HANDLE_VALUE; + } + else + SUP_DPRINTF(("supR3HardenedWinVerifyCacheProcessImportTodos: RTStrToUtf16Ex #2 failed: %Rrc\n", rc)); + } + } + } + + /* + * If we successfully opened it, verify it and cache the result. + */ + if (hFile != INVALID_HANDLE_VALUE) + { + SUP_DPRINTF(("supR3HardenedWinVerifyCacheProcessImportTodos: '%s' -> '%ls' [rcNtRedir=%#x]\n", + pCur->szName, wszPath, rcNtRedir)); + + ULONG fAccess = 0; + ULONG fProtect = 0; + bool fCallRealApi = false; + rcNt = supR3HardenedScreenImage(hFile, true /*fImage*/, false /*fIgnoreArch*/, &fAccess, &fProtect, + &fCallRealApi, "Imports", false /*fAvoidWinVerifyTrust*/, NULL /*pfQuiet*/); + NtClose(hFile); + } + else + SUP_DPRINTF(("supR3HardenedWinVerifyCacheProcessImportTodos: Failed to locate '%s'\n", pCur->szName)); + } + else + SUP_DPRINTF(("supR3HardenedWinVerifyCacheProcessImportTodos: '%s' is in the cache.\n", pCur->szName)); + + RTMemFree(pCur); + } while (pTodo); + } +} + + +/** + * Processes the list of WinVerifyTrust todos. + */ +static void supR3HardenedWinVerifyCacheProcessWvtTodos(void) +{ + PVERIFIERCACHEENTRY pReschedule = NULL; + PVERIFIERCACHEENTRY volatile *ppReschedLastNext = &pReschedule; + + /* + * Work until we've got nothing more todo. + */ + for (;;) + { + if (!supHardenedWinIsWinVerifyTrustCallable()) + break; + PVERIFIERCACHEENTRY pTodo = ASMAtomicXchgPtrT(&g_pVerifierCacheTodoWvt, NULL, PVERIFIERCACHEENTRY); + if (!pTodo) + break; + do + { + PVERIFIERCACHEENTRY pCur = pTodo; + pTodo = pTodo->pNextTodoWvt; + pCur->pNextTodoWvt = NULL; + + if ( !pCur->fWinVerifyTrust + && RT_SUCCESS(pCur->rc)) + { + bool fWinVerifyTrust = false; + int rc = supHardenedWinVerifyImageTrust(pCur->hFile, pCur->wszPath, pCur->fFlags, pCur->rc, + &fWinVerifyTrust, NULL /* pErrInfo*/); + if (RT_FAILURE(rc) || fWinVerifyTrust) + { + SUP_DPRINTF(("supR3HardenedWinVerifyCacheProcessWvtTodos: %d (was %d) fWinVerifyTrust=%d for '%ls'\n", + rc, pCur->rc, fWinVerifyTrust, pCur->wszPath)); + pCur->fWinVerifyTrust = true; + pCur->rc = rc; + } + else + { + /* Retry it at a later time. */ + SUP_DPRINTF(("supR3HardenedWinVerifyCacheProcessWvtTodos: %d (was %d) fWinVerifyTrust=%d for '%ls' [rescheduled]\n", + rc, pCur->rc, fWinVerifyTrust, pCur->wszPath)); + *ppReschedLastNext = pCur; + ppReschedLastNext = &pCur->pNextTodoWvt; + } + } + /* else: already processed. */ + } while (pTodo); + } + + /* + * Anything to reschedule. + */ + if (pReschedule) + { + do + *ppReschedLastNext = g_pVerifierCacheTodoWvt; + while (!ASMAtomicCmpXchgPtr(&g_pVerifierCacheTodoWvt, pReschedule, *ppReschedLastNext)); + } +} + + +/** + * Translates VBox status code (from supHardenedWinVerifyImageTrust) to an NT + * status. + * + * @returns NT status. + * @param rc VBox status code. + */ +static NTSTATUS supR3HardenedScreenImageCalcStatus(int rc) RT_NOTHROW_DEF +{ + /* This seems to be what LdrLoadDll returns when loading a 32-bit DLL into + a 64-bit process. At least here on windows 10 (2015-11-xx). + + NtCreateSection probably returns something different, possibly a warning, + we currently don't distinguish between the too, so we stick with the + LdrLoadDll one as it's definitely an error.*/ + if (rc == VERR_LDR_ARCH_MISMATCH) + return STATUS_INVALID_IMAGE_FORMAT; + + return STATUS_TRUST_FAILURE; +} + + +/** + * Screens an image file or file mapped with execute access. + * + * @returns NT status code. + * @param hFile The file handle. + * @param fImage Set if image file mapping being made + * (NtCreateSection thing). + * @param fIgnoreArch Using the DONT_RESOLVE_DLL_REFERENCES flag, + * which also implies that DLL init / term code + * isn't called, so the architecture should be + * ignored. + * @param pfAccess Pointer to the NtCreateSection access flags, + * so we can modify them if necessary. + * @param pfProtect Pointer to the NtCreateSection protection + * flags, so we can modify them if necessary. + * @param pfCallRealApi Whether it's ok to go on to the real API. + * @param pszCaller Who is calling (for debugging / logging). + * @param fAvoidWinVerifyTrust Whether we should avoid WinVerifyTrust. + * @param pfQuiet Where to return whether to be quiet about + * this image in the log (i.e. we've seen it + * lots of times already). Optional. + */ +static NTSTATUS +supR3HardenedScreenImage(HANDLE hFile, bool fImage, bool fIgnoreArch, PULONG pfAccess, PULONG pfProtect, + bool *pfCallRealApi, const char *pszCaller, bool fAvoidWinVerifyTrust, bool *pfQuiet) RT_NOTHROW_DEF +{ + *pfCallRealApi = false; + if (pfQuiet) + *pfQuiet = false; + + /* + * Query the name of the file, making sure to zero terminator the + * string. (2nd half of buffer is used for error info, see below.) + */ + union + { + UNICODE_STRING UniStr; + uint8_t abBuffer[sizeof(UNICODE_STRING) + 2048 * sizeof(WCHAR)]; + } uBuf; + RT_ZERO(uBuf); + ULONG cbNameBuf; + NTSTATUS rcNt = NtQueryObject(hFile, ObjectNameInformation, &uBuf, sizeof(uBuf) - sizeof(WCHAR) - 128, &cbNameBuf); + if (!NT_SUCCESS(rcNt)) + { + supR3HardenedError(VINF_SUCCESS, false, + "supR3HardenedScreenImage/%s: NtQueryObject -> %#x (fImage=%d fProtect=%#x fAccess=%#x)\n", + pszCaller, fImage, *pfProtect, *pfAccess); + return rcNt; + } + + if (!RTNtPathFindPossible8dot3Name(uBuf.UniStr.Buffer)) + cbNameBuf += sizeof(WCHAR); + else + { + uBuf.UniStr.MaximumLength = sizeof(uBuf) - 128; + RTNtPathExpand8dot3Path(&uBuf.UniStr, true /*fPathOnly*/); + cbNameBuf = (uintptr_t)uBuf.UniStr.Buffer + uBuf.UniStr.Length + sizeof(WCHAR) - (uintptr_t)&uBuf.abBuffer[0]; + } + + /* + * Check the cache. + */ + PVERIFIERCACHEENTRY pCacheHit = supR3HardenedWinVerifyCacheLookup(&uBuf.UniStr, hFile); + if (pCacheHit) + { + /* Do hit accounting and figure whether we need to be quiet or not. */ + uint32_t cHits = ASMAtomicIncU32(&pCacheHit->cHits); + bool const fQuiet = cHits >= 8 && !RT_IS_POWER_OF_TWO(cHits); + if (pfQuiet) + *pfQuiet = fQuiet; + + /* If we haven't done the WinVerifyTrust thing, do it if we can. */ + if ( !pCacheHit->fWinVerifyTrust + && RT_SUCCESS(pCacheHit->rc) + && supHardenedWinIsWinVerifyTrustCallable() ) + { + if (!fAvoidWinVerifyTrust) + { + SUP_DPRINTF(("supR3HardenedScreenImage/%s: cache hit (%Rrc) on %ls [redoing WinVerifyTrust]\n", + pszCaller, pCacheHit->rc, pCacheHit->wszPath)); + + bool fWinVerifyTrust = false; + int rc = supHardenedWinVerifyImageTrust(pCacheHit->hFile, pCacheHit->wszPath, pCacheHit->fFlags, pCacheHit->rc, + &fWinVerifyTrust, NULL /* pErrInfo*/); + if (RT_FAILURE(rc) || fWinVerifyTrust) + { + SUP_DPRINTF(("supR3HardenedScreenImage/%s: %d (was %d) fWinVerifyTrust=%d for '%ls'\n", + pszCaller, rc, pCacheHit->rc, fWinVerifyTrust, pCacheHit->wszPath)); + pCacheHit->fWinVerifyTrust = true; + pCacheHit->rc = rc; + } + else + SUP_DPRINTF(("supR3HardenedScreenImage/%s: WinVerifyTrust not available, rescheduling %ls\n", + pszCaller, pCacheHit->wszPath)); + } + else + SUP_DPRINTF(("supR3HardenedScreenImage/%s: cache hit (%Rrc) on %ls [avoiding WinVerifyTrust]\n", + pszCaller, pCacheHit->rc, pCacheHit->wszPath)); + } + else if (!fQuiet || !pCacheHit->fWinVerifyTrust) + SUP_DPRINTF(("supR3HardenedScreenImage/%s: cache hit (%Rrc) on %ls%s\n", + pszCaller, pCacheHit->rc, pCacheHit->wszPath, pCacheHit->fWinVerifyTrust ? "" : " [lacks WinVerifyTrust]")); + + /* Return the cached value. */ + if (RT_SUCCESS(pCacheHit->rc)) + { + *pfCallRealApi = true; + return STATUS_SUCCESS; + } + + if (!fQuiet) + supR3HardenedError(VINF_SUCCESS, false, + "supR3HardenedScreenImage/%s: cached rc=%Rrc fImage=%d fProtect=%#x fAccess=%#x cHits=%u %ls\n", + pszCaller, pCacheHit->rc, fImage, *pfProtect, *pfAccess, cHits, uBuf.UniStr.Buffer); + return supR3HardenedScreenImageCalcStatus(pCacheHit->rc); + } + + /* + * On XP the loader might hand us handles with just FILE_EXECUTE and + * SYNCHRONIZE, the means reading will fail later on. Also, we need + * READ_CONTROL access to check the file ownership later on, and non + * of the OS versions seems be giving us that. So, in effect we + * more or less always reopen the file here. + */ + HANDLE hMyFile = NULL; + rcNt = NtDuplicateObject(NtCurrentProcess(), hFile, NtCurrentProcess(), + &hMyFile, + FILE_READ_DATA | READ_CONTROL | SYNCHRONIZE, + 0 /* Handle attributes*/, 0 /* Options */); + if (!NT_SUCCESS(rcNt)) + { + if (rcNt == STATUS_ACCESS_DENIED) + { + IO_STATUS_BLOCK Ios = RTNT_IO_STATUS_BLOCK_INITIALIZER; + OBJECT_ATTRIBUTES ObjAttr; + InitializeObjectAttributes(&ObjAttr, &uBuf.UniStr, OBJ_CASE_INSENSITIVE, NULL /*hRootDir*/, NULL /*pSecDesc*/); + + rcNt = NtCreateFile(&hMyFile, + FILE_READ_DATA | READ_CONTROL | 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)) + { + supR3HardenedError(VINF_SUCCESS, false, + "supR3HardenedScreenImage/%s: Failed to duplicate and open the file: rcNt=%#x hFile=%p %ls\n", + pszCaller, rcNt, hFile, uBuf.UniStr.Buffer); + return rcNt; + } + + /* Check that we've got the same file. */ + LARGE_INTEGER idMyFile, idInFile; + bool fMyValid = supR3HardenedWinVerifyCacheGetIndexNumber(hMyFile, &idMyFile); + bool fInValid = supR3HardenedWinVerifyCacheGetIndexNumber(hFile, &idInFile); + if ( fMyValid + && ( fMyValid != fInValid + || idMyFile.QuadPart != idInFile.QuadPart)) + { + supR3HardenedError(VINF_SUCCESS, false, + "supR3HardenedScreenImage/%s: Re-opened has different ID that input: %#llx vx %#llx (%ls)\n", + pszCaller, rcNt, idMyFile.QuadPart, idInFile.QuadPart, uBuf.UniStr.Buffer); + NtClose(hMyFile); + return STATUS_TRUST_FAILURE; + } + } + else + { + SUP_DPRINTF(("supR3HardenedScreenImage/%s: NtDuplicateObject -> %#x\n", pszCaller, rcNt)); +#ifdef DEBUG + + supR3HardenedError(VINF_SUCCESS, false, + "supR3HardenedScreenImage/%s: NtDuplicateObject(,%#x,) failed: %#x\n", pszCaller, hFile, rcNt); +#endif + hMyFile = hFile; + } + } + + /* + * Special Kludge for Windows XP and W2K3 and their stupid attempts + * at mapping a hidden XML file called c:\Windows\WindowsShell.Manifest + * with executable access. The image bit isn't set, fortunately. + */ + if ( !fImage + && uBuf.UniStr.Length > g_System32NtPath.UniStr.Length - sizeof(L"System32") + sizeof(WCHAR) + && memcmp(uBuf.UniStr.Buffer, g_System32NtPath.UniStr.Buffer, + g_System32NtPath.UniStr.Length - sizeof(L"System32") + sizeof(WCHAR)) == 0) + { + PRTUTF16 pwszName = &uBuf.UniStr.Buffer[(g_System32NtPath.UniStr.Length - sizeof(L"System32") + sizeof(WCHAR)) / sizeof(WCHAR)]; + if (RTUtf16ICmpAscii(pwszName, "WindowsShell.Manifest") == 0) + { + /* + * Drop all executable access to the mapping and let it continue. + */ + SUP_DPRINTF(("supR3HardenedScreenImage/%s: Applying the drop-exec-kludge for '%ls'\n", pszCaller, uBuf.UniStr.Buffer)); + if (*pfAccess & SECTION_MAP_EXECUTE) + *pfAccess = (*pfAccess & ~SECTION_MAP_EXECUTE) | SECTION_MAP_READ; + if (*pfProtect & PAGE_EXECUTE) + *pfProtect = (*pfProtect & ~PAGE_EXECUTE) | PAGE_READONLY; + *pfProtect = (*pfProtect & ~UINT32_C(0xf0)) | ((*pfProtect & UINT32_C(0xe0)) >> 4); + if (hMyFile != hFile) + NtClose(hMyFile); + *pfCallRealApi = true; + return STATUS_SUCCESS; + } + } + +#ifndef VBOX_PERMIT_EVEN_MORE + /* + * Check the path. We don't allow DLLs to be loaded from just anywhere: + * 1. System32 - normal code or cat signing, owner TrustedInstaller. + * 2. WinSxS - normal code or cat signing, owner TrustedInstaller. + * 3. VirtualBox - kernel code signing and integrity checks. + * 4. AppPatchDir - normal code or cat signing, owner TrustedInstaller. + * 5. Program Files - normal code or cat signing, owner TrustedInstaller. + * 6. Common Files - normal code or cat signing, owner TrustedInstaller. + * 7. x86 variations of 4 & 5 - ditto. + */ + uint32_t fFlags = 0; + if (supHardViUniStrPathStartsWithUniStr(&uBuf.UniStr, &g_System32NtPath.UniStr, true /*fCheckSlash*/)) + fFlags |= SUPHNTVI_F_ALLOW_CAT_FILE_VERIFICATION | SUPHNTVI_F_TRUSTED_INSTALLER_OWNER; + else if (supHardViUniStrPathStartsWithUniStr(&uBuf.UniStr, &g_WinSxSNtPath.UniStr, true /*fCheckSlash*/)) + fFlags |= SUPHNTVI_F_ALLOW_CAT_FILE_VERIFICATION | SUPHNTVI_F_TRUSTED_INSTALLER_OWNER; + else if (supHardViUniStrPathStartsWithUniStr(&uBuf.UniStr, &g_SupLibHardenedAppBinNtPath.UniStr, true /*fCheckSlash*/)) + fFlags |= SUPHNTVI_F_REQUIRE_KERNEL_CODE_SIGNING | SUPHNTVI_F_REQUIRE_SIGNATURE_ENFORCEMENT; +# ifdef VBOX_PERMIT_MORE + else if (supHardViIsAppPatchDir(uBuf.UniStr.Buffer, uBuf.UniStr.Length / sizeof(WCHAR))) + fFlags |= SUPHNTVI_F_ALLOW_CAT_FILE_VERIFICATION | SUPHNTVI_F_TRUSTED_INSTALLER_OWNER; + else if (supHardViUniStrPathStartsWithUniStr(&uBuf.UniStr, &g_ProgramFilesNtPath.UniStr, true /*fCheckSlash*/)) + fFlags |= SUPHNTVI_F_ALLOW_CAT_FILE_VERIFICATION | SUPHNTVI_F_TRUSTED_INSTALLER_OWNER; + else if (supHardViUniStrPathStartsWithUniStr(&uBuf.UniStr, &g_CommonFilesNtPath.UniStr, true /*fCheckSlash*/)) + fFlags |= SUPHNTVI_F_ALLOW_CAT_FILE_VERIFICATION | SUPHNTVI_F_TRUSTED_INSTALLER_OWNER; +# ifdef RT_ARCH_AMD64 + else if (supHardViUniStrPathStartsWithUniStr(&uBuf.UniStr, &g_ProgramFilesX86NtPath.UniStr, true /*fCheckSlash*/)) + fFlags |= SUPHNTVI_F_ALLOW_CAT_FILE_VERIFICATION | SUPHNTVI_F_TRUSTED_INSTALLER_OWNER; + else if (supHardViUniStrPathStartsWithUniStr(&uBuf.UniStr, &g_CommonFilesX86NtPath.UniStr, true /*fCheckSlash*/)) + fFlags |= SUPHNTVI_F_ALLOW_CAT_FILE_VERIFICATION | SUPHNTVI_F_TRUSTED_INSTALLER_OWNER; +# endif +# endif +# ifdef VBOX_PERMIT_VISUAL_STUDIO_PROFILING + /* Hack to allow profiling our code with Visual Studio. */ + else if ( uBuf.UniStr.Length > sizeof(L"\\SamplingRuntime.dll") + && memcmp(uBuf.UniStr.Buffer + (uBuf.UniStr.Length - sizeof(L"\\SamplingRuntime.dll") + sizeof(WCHAR)) / sizeof(WCHAR), + L"\\SamplingRuntime.dll", sizeof(L"\\SamplingRuntime.dll") - sizeof(WCHAR)) == 0 ) + { + if (hMyFile != hFile) + NtClose(hMyFile); + *pfCallRealApi = true; + return STATUS_SUCCESS; + } +# endif + else + { + supR3HardenedError(VINF_SUCCESS, false, + "supR3HardenedScreenImage/%s: Not a trusted location: '%ls' (fImage=%d fProtect=%#x fAccess=%#x)\n", + pszCaller, uBuf.UniStr.Buffer, fImage, *pfAccess, *pfProtect); + if (hMyFile != hFile) + NtClose(hMyFile); + return STATUS_TRUST_FAILURE; + } + +#else /* VBOX_PERMIT_EVEN_MORE */ + /* + * Require trusted installer + some kind of signature on everything, except + * for the VBox bits where we require kernel code signing and special + * integrity checks. + */ + uint32_t fFlags = 0; + if (supHardViUniStrPathStartsWithUniStr(&uBuf.UniStr, &g_SupLibHardenedAppBinNtPath.UniStr, true /*fCheckSlash*/)) + fFlags |= SUPHNTVI_F_REQUIRE_KERNEL_CODE_SIGNING | SUPHNTVI_F_REQUIRE_SIGNATURE_ENFORCEMENT; + else + fFlags |= SUPHNTVI_F_ALLOW_CAT_FILE_VERIFICATION | SUPHNTVI_F_TRUSTED_INSTALLER_OWNER; +#endif /* VBOX_PERMIT_EVEN_MORE */ + + /* + * Do the verification. For better error message we borrow what's + * left of the path buffer for an RTERRINFO buffer. + */ + if (fIgnoreArch) + fFlags |= SUPHNTVI_F_IGNORE_ARCHITECTURE; + RTERRINFO ErrInfo; + RTErrInfoInit(&ErrInfo, (char *)&uBuf.abBuffer[cbNameBuf], sizeof(uBuf) - cbNameBuf); + + int rc; + bool fWinVerifyTrust = false; + rc = supHardenedWinVerifyImageByHandle(hMyFile, uBuf.UniStr.Buffer, fFlags, fAvoidWinVerifyTrust, &fWinVerifyTrust, &ErrInfo); + if (RT_FAILURE(rc)) + { + supR3HardenedError(VINF_SUCCESS, false, + "supR3HardenedScreenImage/%s: rc=%Rrc fImage=%d fProtect=%#x fAccess=%#x %ls: %s\n", + pszCaller, rc, fImage, *pfAccess, *pfProtect, uBuf.UniStr.Buffer, ErrInfo.pszMsg); + if (hMyFile != hFile) + supR3HardenedWinVerifyCacheInsert(&uBuf.UniStr, hMyFile, rc, fWinVerifyTrust, fFlags); + return supR3HardenedScreenImageCalcStatus(rc); + } + + /* + * Insert into the cache. + */ + if (hMyFile != hFile) + supR3HardenedWinVerifyCacheInsert(&uBuf.UniStr, hMyFile, rc, fWinVerifyTrust, fFlags); + + *pfCallRealApi = true; + return STATUS_SUCCESS; +} + + +/** + * Preloads a file into the verify cache if possible. + * + * This is used to avoid known cyclic LoadLibrary issues with WinVerifyTrust. + * + * @param pwszName The name of the DLL to verify. + */ +DECLHIDDEN(void) supR3HardenedWinVerifyCachePreload(PCRTUTF16 pwszName) +{ + HANDLE hFile = RTNT_INVALID_HANDLE_VALUE; + IO_STATUS_BLOCK Ios = RTNT_IO_STATUS_BLOCK_INITIALIZER; + + UNICODE_STRING UniStr; + UniStr.Buffer = (PWCHAR)pwszName; + UniStr.Length = (USHORT)(RTUtf16Len(pwszName) * sizeof(WCHAR)); + UniStr.MaximumLength = UniStr.Length + sizeof(WCHAR); + + OBJECT_ATTRIBUTES ObjAttr; + InitializeObjectAttributes(&ObjAttr, &UniStr, OBJ_CASE_INSENSITIVE, NULL /*hRootDir*/, NULL /*pSecDesc*/); + + NTSTATUS rcNt = NtCreateFile(&hFile, + FILE_READ_DATA | READ_CONTROL | 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)) + { + SUP_DPRINTF(("supR3HardenedWinVerifyCachePreload: Error %#x opening '%ls'.\n", rcNt, pwszName)); + return; + } + + ULONG fAccess = 0; + ULONG fProtect = 0; + bool fCallRealApi; + //SUP_DPRINTF(("supR3HardenedWinVerifyCachePreload: scanning %ls\n", pwszName)); + supR3HardenedScreenImage(hFile, false, false /*fIgnoreArch*/, &fAccess, &fProtect, &fCallRealApi, "preload", + false /*fAvoidWinVerifyTrust*/, NULL /*pfQuiet*/); + //SUP_DPRINTF(("supR3HardenedWinVerifyCachePreload: done %ls\n", pwszName)); + + NtClose(hFile); +} + + + +/** + * Hook that monitors NtCreateSection calls. + * + * @returns NT status code. + * @param phSection Where to return the section handle. + * @param fAccess The desired access. + * @param pObjAttribs The object attributes (optional). + * @param pcbSection The section size (optional). + * @param fProtect The max section protection. + * @param fAttribs The section attributes. + * @param hFile The file to create a section from (optional). + */ +__declspec(guard(ignore)) /* don't barf when calling g_pfnNtCreateSectionReal */ +static NTSTATUS NTAPI +supR3HardenedMonitor_NtCreateSection(PHANDLE phSection, ACCESS_MASK fAccess, POBJECT_ATTRIBUTES pObjAttribs, + PLARGE_INTEGER pcbSection, ULONG fProtect, ULONG fAttribs, HANDLE hFile) +{ + bool fNeedUncChecking = false; + if ( hFile != NULL + && hFile != INVALID_HANDLE_VALUE) + { + bool const fImage = RT_BOOL(fAttribs & (SEC_IMAGE | SEC_PROTECTED_IMAGE)); + bool const fExecMap = RT_BOOL(fAccess & SECTION_MAP_EXECUTE); + bool const fExecProt = RT_BOOL(fProtect & (PAGE_EXECUTE | PAGE_EXECUTE_READ | PAGE_EXECUTE_WRITECOPY + | PAGE_EXECUTE_READWRITE)); + if (fImage || fExecMap || fExecProt) + { + fNeedUncChecking = true; + DWORD dwSavedLastError = RtlGetLastWin32Error(); + + bool fCallRealApi; + //SUP_DPRINTF(("supR3HardenedMonitor_NtCreateSection: 1\n")); + NTSTATUS rcNt = supR3HardenedScreenImage(hFile, fImage, true /*fIgnoreArch*/, &fAccess, &fProtect, &fCallRealApi, + "NtCreateSection", true /*fAvoidWinVerifyTrust*/, NULL /*pfQuiet*/); + //SUP_DPRINTF(("supR3HardenedMonitor_NtCreateSection: 2 rcNt=%#x fCallRealApi=%#x\n", rcNt, fCallRealApi)); + + RtlRestoreLastWin32Error(dwSavedLastError); + + if (!NT_SUCCESS(rcNt)) + return rcNt; + Assert(fCallRealApi); + if (!fCallRealApi) + return STATUS_TRUST_FAILURE; + + } + } + + /* + * Call checked out OK, call the original. + */ + NTSTATUS rcNtReal = g_pfnNtCreateSectionReal(phSection, fAccess, pObjAttribs, pcbSection, fProtect, fAttribs, hFile); + + /* + * Check that the image that got mapped bear some resemblance to the one that was + * requested. Apparently there are ways to trick the NT cache manager to map a + * file different from hFile into memory using local UNC accesses. + */ + if ( NT_SUCCESS(rcNtReal) + && fNeedUncChecking) + { + DWORD dwSavedLastError = RtlGetLastWin32Error(); + + bool fOkay = false; + + /* To get the name of the file backing the section, we unfortunately have to map it. */ + SIZE_T cbView = 0; + PVOID pvTmpMap = NULL; + NTSTATUS rcNt = NtMapViewOfSection(*phSection, NtCurrentProcess(), &pvTmpMap, 0, 0, NULL /*poffSection*/, &cbView, + ViewUnmap, MEM_TOP_DOWN, PAGE_EXECUTE); + if (NT_SUCCESS(rcNt)) + { + /* Query the name. */ + union + { + UNICODE_STRING UniStr; + RTUTF16 awcBuf[512]; + } uBuf; + RT_ZERO(uBuf); + SIZE_T cbActual = 0; + NTSTATUS rcNtQuery = NtQueryVirtualMemory(NtCurrentProcess(), pvTmpMap, MemorySectionName, + &uBuf, sizeof(uBuf) - sizeof(RTUTF16), &cbActual); + + /* Unmap the view. */ + rcNt = NtUnmapViewOfSection(NtCurrentProcess(), pvTmpMap); + if (!NT_SUCCESS(rcNt)) + SUP_DPRINTF(("supR3HardenedMonitor_NtCreateSection: NtUnmapViewOfSection failed on %p (hSection=%p, hFile=%p) with %#x!\n", + pvTmpMap, *phSection, hFile, rcNt)); + + /* Process the name query result. */ + if (NT_SUCCESS(rcNtQuery)) + { + static UNICODE_STRING const s_UncPrefix = RTNT_CONSTANT_UNISTR(L"\\Device\\Mup"); + if (!supHardViUniStrPathStartsWithUniStr(&uBuf.UniStr, &s_UncPrefix, true /*fCheckSlash*/)) + fOkay = true; + else + supR3HardenedError(VINF_SUCCESS, false, + "supR3HardenedMonitor_NtCreateSection: Image section with UNC path is not trusted: '%.*ls'\n", + uBuf.UniStr.Length / sizeof(RTUTF16), uBuf.UniStr.Buffer); + } + else + SUP_DPRINTF(("supR3HardenedMonitor_NtCreateSection: NtQueryVirtualMemory failed on %p (hFile=%p) with %#x -> STATUS_TRUST_FAILURE\n", + *phSection, hFile, rcNt)); + } + else + SUP_DPRINTF(("supR3HardenedMonitor_NtCreateSection: NtMapViewOfSection failed on %p (hFile=%p) with %#x -> STATUS_TRUST_FAILURE\n", + *phSection, hFile, rcNt)); + if (!fOkay) + { + NtClose(*phSection); + *phSection = INVALID_HANDLE_VALUE; + RtlRestoreLastWin32Error(dwSavedLastError); + return STATUS_TRUST_FAILURE; + } + + RtlRestoreLastWin32Error(dwSavedLastError); + } + return rcNtReal; +} + + +/** + * Checks if the given name is a valid ApiSet name. + * + * This is only called on likely looking names. + * + * @returns true if ApiSet name, false if not. + * @param pName The name to check out. + */ +static bool supR3HardenedIsApiSetDll(PUNICODE_STRING pName) +{ + /* + * API added in Windows 8, or so they say. + */ + if (ApiSetQueryApiSetPresence != NULL) + { + BOOLEAN fPresent = FALSE; + NTSTATUS rcNt = ApiSetQueryApiSetPresence(pName, &fPresent); + SUP_DPRINTF(("supR3HardenedIsApiSetDll: ApiSetQueryApiSetPresence(%.*ls) -> %#x, fPresent=%d\n", + pName->Length / sizeof(WCHAR), pName->Buffer, rcNt, fPresent)); + return fPresent != 0; + } + + /* + * Fallback needed for Windows 7. Fortunately, there aren't too many fake DLLs here. + */ + if ( g_uNtVerCombined >= SUP_NT_VER_W70 + && ( supHardViUtf16PathStartsWithEx(pName->Buffer, pName->Length / sizeof(WCHAR), + L"api-ms-win-", 11, false /*fCheckSlash*/) + || supHardViUtf16PathStartsWithEx(pName->Buffer, pName->Length / sizeof(WCHAR), + L"ext-ms-win-", 11, false /*fCheckSlash*/) )) + { +#define MY_ENTRY(a) { a, sizeof(a) - 1 } + static const struct { const char *psz; size_t cch; } s_aKnownSets[] = + { + MY_ENTRY("api-ms-win-core-console-l1-1-0 "), + MY_ENTRY("api-ms-win-core-datetime-l1-1-0"), + MY_ENTRY("api-ms-win-core-debug-l1-1-0"), + MY_ENTRY("api-ms-win-core-delayload-l1-1-0"), + MY_ENTRY("api-ms-win-core-errorhandling-l1-1-0"), + MY_ENTRY("api-ms-win-core-fibers-l1-1-0"), + MY_ENTRY("api-ms-win-core-file-l1-1-0"), + MY_ENTRY("api-ms-win-core-handle-l1-1-0"), + MY_ENTRY("api-ms-win-core-heap-l1-1-0"), + MY_ENTRY("api-ms-win-core-interlocked-l1-1-0"), + MY_ENTRY("api-ms-win-core-io-l1-1-0"), + MY_ENTRY("api-ms-win-core-libraryloader-l1-1-0"), + MY_ENTRY("api-ms-win-core-localization-l1-1-0"), + MY_ENTRY("api-ms-win-core-localregistry-l1-1-0"), + MY_ENTRY("api-ms-win-core-memory-l1-1-0"), + MY_ENTRY("api-ms-win-core-misc-l1-1-0"), + MY_ENTRY("api-ms-win-core-namedpipe-l1-1-0"), + MY_ENTRY("api-ms-win-core-processenvironment-l1-1-0"), + MY_ENTRY("api-ms-win-core-processthreads-l1-1-0"), + MY_ENTRY("api-ms-win-core-profile-l1-1-0"), + MY_ENTRY("api-ms-win-core-rtlsupport-l1-1-0"), + MY_ENTRY("api-ms-win-core-string-l1-1-0"), + MY_ENTRY("api-ms-win-core-synch-l1-1-0"), + MY_ENTRY("api-ms-win-core-sysinfo-l1-1-0"), + MY_ENTRY("api-ms-win-core-threadpool-l1-1-0"), + MY_ENTRY("api-ms-win-core-ums-l1-1-0"), + MY_ENTRY("api-ms-win-core-util-l1-1-0"), + MY_ENTRY("api-ms-win-core-xstate-l1-1-0"), + MY_ENTRY("api-ms-win-security-base-l1-1-0"), + MY_ENTRY("api-ms-win-security-lsalookup-l1-1-0"), + MY_ENTRY("api-ms-win-security-sddl-l1-1-0"), + MY_ENTRY("api-ms-win-service-core-l1-1-0"), + MY_ENTRY("api-ms-win-service-management-l1-1-0"), + MY_ENTRY("api-ms-win-service-management-l2-1-0"), + MY_ENTRY("api-ms-win-service-winsvc-l1-1-0"), + }; +#undef MY_ENTRY + + /* drop the dll suffix if present. */ + PCRTUTF16 pawcName = pName->Buffer; + size_t cwcName = pName->Length / sizeof(WCHAR); + if ( cwcName > 5 + && (pawcName[cwcName - 1] == 'l' || pawcName[cwcName - 1] == 'L') + && (pawcName[cwcName - 2] == 'l' || pawcName[cwcName - 2] == 'L') + && (pawcName[cwcName - 3] == 'd' || pawcName[cwcName - 3] == 'D') + && pawcName[cwcName - 4] == '.') + cwcName -= 4; + + /* Search the table. */ + for (size_t i = 0; i < RT_ELEMENTS(s_aKnownSets); i++) + if ( cwcName == s_aKnownSets[i].cch + && RTUtf16NICmpAscii(pawcName, s_aKnownSets[i].psz, cwcName) == 0) + { + SUP_DPRINTF(("supR3HardenedIsApiSetDll: '%.*ls' -> true\n", pName->Length / sizeof(WCHAR))); + return true; + } + + SUP_DPRINTF(("supR3HardenedIsApiSetDll: Warning! '%.*ls' looks like an API set, but it's not in the list!\n", + pName->Length / sizeof(WCHAR), pName->Buffer)); + } + + SUP_DPRINTF(("supR3HardenedIsApiSetDll: '%.*ls' -> false\n", pName->Length / sizeof(WCHAR))); + return false; +} + + +/** + * Checks whether the given unicode string contains a path separator and at + * least one dash. + * + * This is used to check for likely ApiSet name. So far, all the pseudo DLL + * names include multiple dashes, so we use that as a criteria for recognizing + * them. By happy coincident, most regular DLLs doesn't include dashes. + * + * @returns true if it contains path separator, false if only a name. + * @param pPath The path to check. + */ +static bool supR3HardenedHasDashButNoPath(PUNICODE_STRING pPath) +{ + size_t cDashes = 0; + size_t cwcLeft = pPath->Length / sizeof(WCHAR); + PCRTUTF16 pwc = pPath->Buffer; + while (cwcLeft-- > 0) + { + RTUTF16 wc = *pwc++; + switch (wc) + { + default: + break; + + case '-': + cDashes++; + break; + + case '\\': + case '/': + case ':': + return false; + } + } + return cDashes > 0; +} + + +/** + * Helper for supR3HardenedMonitor_LdrLoadDll. + * + * @returns NT status code. + * @param pwszPath The path destination buffer. + * @param cwcPath The size of the path buffer. + * @param pUniStrResult The result string. + * @param pOrgName The orignal name (for errors). + * @param pcwc Where to return the actual length. + */ +static NTSTATUS supR3HardenedCopyRedirectionResult(WCHAR *pwszPath, size_t cwcPath, PUNICODE_STRING pUniStrResult, + PUNICODE_STRING pOrgName, UINT *pcwc) +{ + UINT cwc; + *pcwc = cwc = pUniStrResult->Length / sizeof(WCHAR); + if (pUniStrResult->Buffer == pwszPath) + pwszPath[cwc] = '\0'; + else + { + if (cwc > cwcPath - 1) + { + supR3HardenedError(VINF_SUCCESS, false, + "supR3HardenedMonitor_LdrLoadDll: Name too long: %.*ls -> %.*ls (RtlDosApplyFileIoslationRedirection_Ustr)\n", + pOrgName->Length / sizeof(WCHAR), pOrgName->Buffer, + pUniStrResult->Length / sizeof(WCHAR), pUniStrResult->Buffer); + return STATUS_NAME_TOO_LONG; + } + memcpy(&pwszPath[0], pUniStrResult->Buffer, pUniStrResult->Length); + pwszPath[cwc] = '\0'; + } + return STATUS_SUCCESS; +} + + +/** + * Helper for supR3HardenedMonitor_LdrLoadDll that compares the name part of the + * input path against a ASCII name string of a given length. + * + * @returns true if the name part matches + * @param pPath The LdrLoadDll input path. + * @param pszName The name to try match it with. + * @param cchName The name length. + */ +static bool supR3HardenedIsFilenameMatchDll(PUNICODE_STRING pPath, const char *pszName, size_t cchName) +{ + if (pPath->Length < cchName * 2) + return false; + PCRTUTF16 pwszTmp = &pPath->Buffer[pPath->Length / sizeof(RTUTF16) - cchName]; + if ( pPath->Length != cchName + && pwszTmp[-1] != '\\' + && pwszTmp[-1] != '/') + return false; + return RTUtf16ICmpAscii(pwszTmp, pszName) == 0; +} + + +/** + * Hooks that intercepts LdrLoadDll calls. + * + * Two purposes: + * -# Enforce our own search path restrictions. + * -# Prevalidate DLLs about to be loaded so we don't upset the loader data + * by doing it from within the NtCreateSection hook (WinVerifyTrust + * seems to be doing harm there on W7/32). + * + * @returns + * @param pwszSearchPath The search path to use. + * @param pfFlags Flags on input. DLL characteristics or something + * on return? + * @param pName The name of the module. + * @param phMod Where the handle of the loaded DLL is to be + * returned to the caller. + */ +__declspec(guard(ignore)) /* don't barf when calling g_pfnLdrLoadDllReal */ +static NTSTATUS NTAPI +supR3HardenedMonitor_LdrLoadDll(PWSTR pwszSearchPath, PULONG pfFlags, PUNICODE_STRING pName, PHANDLE phMod) +{ + DWORD dwSavedLastError = RtlGetLastWin32Error(); + PUNICODE_STRING const pOrgName = pName; + NTSTATUS rcNt; + + /* + * Make sure the DLL notification callback is registered. If we could, we + * would've done this during early process init, but due to lack of heap + * and uninitialized loader lock, it's not possible that early on. + * + * The callback protects our NtDll hooks from getting unhooked by + * "friendly" fire from the AV crowd. + */ + supR3HardenedWinRegisterDllNotificationCallback(); + + /* + * Process WinVerifyTrust todo before and after. + */ + supR3HardenedWinVerifyCacheProcessWvtTodos(); + + /* + * Reject things we don't want to deal with. + */ + if (!pName || pName->Length == 0) + { + supR3HardenedError(VINF_SUCCESS, false, "supR3HardenedMonitor_LdrLoadDll: name is NULL or have a zero length.\n"); + SUP_DPRINTF(("supR3HardenedMonitor_LdrLoadDll: returns rcNt=%#x (pName=%p)\n", STATUS_INVALID_PARAMETER, pName)); + RtlRestoreLastWin32Error(dwSavedLastError); + return STATUS_INVALID_PARAMETER; + } + PCWCHAR const pawcOrgName = pName->Buffer; + uint32_t const cwcOrgName = pName->Length / sizeof(WCHAR); + + /*SUP_DPRINTF(("supR3HardenedMonitor_LdrLoadDll: pName=%.*ls *pfFlags=%#x pwszSearchPath=%p:%ls\n", + (unsigned)pName->Length / sizeof(WCHAR), pName->Buffer, pfFlags ? *pfFlags : UINT32_MAX, pwszSearchPath, + !((uintptr_t)pwszSearchPath & 1) && (uintptr_t)pwszSearchPath >= 0x2000U ? pwszSearchPath : L"<flags>"));*/ + + /* + * Reject long paths that's close to the 260 limit without looking. + */ + if (cwcOrgName > 256) + { + supR3HardenedError(VINF_SUCCESS, false, "supR3HardenedMonitor_LdrLoadDll: too long name: %#x bytes\n", pName->Length); + SUP_DPRINTF(("supR3HardenedMonitor_LdrLoadDll: returns rcNt=%#x\n", STATUS_NAME_TOO_LONG)); + RtlRestoreLastWin32Error(dwSavedLastError); + return STATUS_NAME_TOO_LONG; + } + + /* + * Reject all UNC-like paths as we cannot trust non-local files at all. + * Note! We may have to relax this to deal with long path specifications and NT pass thrus. + */ + if ( cwcOrgName >= 3 + && RTPATH_IS_SLASH(pawcOrgName[0]) + && RTPATH_IS_SLASH(pawcOrgName[1]) + && !RTPATH_IS_SLASH(pawcOrgName[2])) + { + supR3HardenedError(VINF_SUCCESS, false, "supR3HardenedMonitor_LdrLoadDll: rejecting UNC name '%.*ls'\n", cwcOrgName, pawcOrgName); + SUP_DPRINTF(("supR3HardenedMonitor_LdrLoadDll: returns rcNt=%#x\n", STATUS_REDIRECTOR_NOT_STARTED)); + RtlRestoreLastWin32Error(dwSavedLastError); + return STATUS_REDIRECTOR_NOT_STARTED; + } + + /* + * Reject PGHook.dll as it creates a thread from its DllMain that breaks + * our preconditions respawning the 2nd process, resulting in + * VERR_SUP_VP_THREAD_NOT_ALONE. The DLL is being loaded by a user APC + * scheduled during kernel32.dll load notification from a kernel driver, + * so failing the load attempt should not upset anyone. + */ + if (g_enmSupR3HardenedMainState == SUPR3HARDENEDMAINSTATE_WIN_EARLY_STUB_DEVICE_OPENED) + { + static const struct { const char *psz; size_t cch; } s_aUnwantedEarlyDlls[] = + { + { RT_STR_TUPLE("PGHook.dll") }, + }; + for (unsigned i = 0; i < RT_ELEMENTS(s_aUnwantedEarlyDlls); i++) + if (supR3HardenedIsFilenameMatchDll(pName, s_aUnwantedEarlyDlls[i].psz, s_aUnwantedEarlyDlls[i].cch)) + { + SUP_DPRINTF(("supR3HardenedMonitor_LdrLoadDll: Refusing to load '%.*ls' as it is expected to create undesirable threads that will upset our respawn checks (returning STATUS_TOO_MANY_THREADS)\n", + pName->Length / sizeof(RTUTF16), pName->Buffer)); + return STATUS_TOO_MANY_THREADS; + } + } + + /* + * Resolve the path, copying the result into wszPath + */ + NTSTATUS rcNtResolve = STATUS_SUCCESS; + bool fSkipValidation = false; + bool fCheckIfLoaded = false; + WCHAR wszPath[260]; + static UNICODE_STRING const s_DefaultSuffix = RTNT_CONSTANT_UNISTR(L".dll"); + UNICODE_STRING UniStrStatic = { 0, (USHORT)sizeof(wszPath) - sizeof(WCHAR), wszPath }; + UNICODE_STRING UniStrDynamic = { 0, 0, NULL }; + PUNICODE_STRING pUniStrResult = NULL; + UNICODE_STRING ResolvedName; + + /* + * Process the name a little, checking if it needs a DLL suffix and is pathless. + */ + uint32_t offLastSlash = UINT32_MAX; + uint32_t offLastDot = UINT32_MAX; + for (uint32_t i = 0; i < cwcOrgName; i++) + switch (pawcOrgName[i]) + { + case '\\': + case '/': + offLastSlash = i; + offLastDot = UINT32_MAX; + break; + case '.': + offLastDot = i; + break; + } + bool const fNeedDllSuffix = offLastDot == UINT32_MAX; + //bool const fTrailingDot = offLastDot == cwcOrgName - 1; + + /* + * Absolute path? + */ + if ( ( cwcOrgName >= 4 + && RT_C_IS_ALPHA(pawcOrgName[0]) + && pawcOrgName[1] == ':' + && RTPATH_IS_SLASH(pawcOrgName[2]) ) + || ( cwcOrgName >= 1 + && RTPATH_IS_SLASH(pawcOrgName[0]) ) + ) + { + rcNtResolve = RtlDosApplyFileIsolationRedirection_Ustr(1 /*fFlags*/, + pName, + (PUNICODE_STRING)&s_DefaultSuffix, + &UniStrStatic, + &UniStrDynamic, + &pUniStrResult, + NULL /*pNewFlags*/, + NULL /*pcbFilename*/, + NULL /*pcbNeeded*/); + if (NT_SUCCESS(rcNtResolve)) + { + UINT cwc; + rcNt = supR3HardenedCopyRedirectionResult(wszPath, RT_ELEMENTS(wszPath), pUniStrResult, pName, &cwc); + RtlFreeUnicodeString(&UniStrDynamic); + if (!NT_SUCCESS(rcNt)) + { + SUP_DPRINTF(("supR3HardenedMonitor_LdrLoadDll: returns rcNt=%#x\n", rcNt)); + RtlRestoreLastWin32Error(dwSavedLastError); + return rcNt; + } + + ResolvedName.Buffer = wszPath; + ResolvedName.Length = (USHORT)(cwc * sizeof(WCHAR)); + ResolvedName.MaximumLength = ResolvedName.Length + sizeof(WCHAR); + + SUP_DPRINTF(("supR3HardenedMonitor_LdrLoadDll: '%.*ls' -> '%.*ls' [redir]\n", + (unsigned)pName->Length / sizeof(WCHAR), pName->Buffer, + ResolvedName.Length / sizeof(WCHAR), ResolvedName.Buffer, rcNt)); + pName = &ResolvedName; + } + else + { + /* Copy the path. */ + memcpy(wszPath, pawcOrgName, cwcOrgName * sizeof(WCHAR)); + if (!fNeedDllSuffix) + wszPath[cwcOrgName] = '\0'; + else + { + if (cwcOrgName + 4 >= RT_ELEMENTS(wszPath)) + { + supR3HardenedError(VINF_SUCCESS, false, + "supR3HardenedMonitor_LdrLoadDll: Name too long (abs): %.*ls\n", cwcOrgName, pawcOrgName); + SUP_DPRINTF(("supR3HardenedMonitor_LdrLoadDll: returns rcNt=%#x\n", STATUS_NAME_TOO_LONG)); + RtlRestoreLastWin32Error(dwSavedLastError); + return STATUS_NAME_TOO_LONG; + } + memcpy(&wszPath[cwcOrgName], L".dll", 5 * sizeof(WCHAR)); + } + } + } + /* + * Not an absolute path. Check if it's one of those special API set DLLs + * or something we're known to use but should be taken from WinSxS. + */ + else if ( supR3HardenedHasDashButNoPath(pName) + && supR3HardenedIsApiSetDll(pName)) + { + memcpy(wszPath, pName->Buffer, pName->Length); + wszPath[pName->Length / sizeof(WCHAR)] = '\0'; + fSkipValidation = true; + } + /* + * Not an absolute path or special API set. There are two alternatives + * now, either there is no path at all or there is a relative path. We + * will resolve it to an absolute path in either case, failing the call + * if we can't. + */ + else + { + /* + * Reject relative paths for now as they might be breakout attempts. + */ + if (offLastSlash != UINT32_MAX) + { + supR3HardenedError(VINF_SUCCESS, false, + "supR3HardenedMonitor_LdrLoadDll: relative name not permitted: %.*ls\n", + cwcOrgName, pawcOrgName); + SUP_DPRINTF(("supR3HardenedMonitor_LdrLoadDll: returns rcNt=%#x\n", STATUS_OBJECT_NAME_INVALID)); + RtlRestoreLastWin32Error(dwSavedLastError); + return STATUS_OBJECT_NAME_INVALID; + } + + /* + * Perform dll redirection to WinSxS such. We using an undocumented + * API here, which as always is a bit risky... ASSUMES that the API + * returns a full DOS path. + */ + UINT cwc; + rcNtResolve = RtlDosApplyFileIsolationRedirection_Ustr(1 /*fFlags*/, + pName, + (PUNICODE_STRING)&s_DefaultSuffix, + &UniStrStatic, + &UniStrDynamic, + &pUniStrResult, + NULL /*pNewFlags*/, + NULL /*pcbFilename*/, + NULL /*pcbNeeded*/); + if (NT_SUCCESS(rcNtResolve)) + { + rcNt = supR3HardenedCopyRedirectionResult(wszPath, RT_ELEMENTS(wszPath), pUniStrResult, pName, &cwc); + RtlFreeUnicodeString(&UniStrDynamic); + if (!NT_SUCCESS(rcNt)) + { + SUP_DPRINTF(("supR3HardenedMonitor_LdrLoadDll: returns rcNt=%#x\n", rcNt)); + RtlRestoreLastWin32Error(dwSavedLastError); + return rcNt; + } + } + else + { + /* + * Search for the DLL. Only System32 is allowed as the target of + * a search on the API level, all VBox calls will have full paths. + * If the DLL is not in System32, we will resort to check if it's + * refering to an already loaded DLL (fCheckIfLoaded). + */ + AssertCompile(sizeof(g_System32WinPath.awcBuffer) <= sizeof(wszPath)); + cwc = g_System32WinPath.UniStr.Length / sizeof(RTUTF16); Assert(cwc > 2); + if (cwc + 1 + cwcOrgName + fNeedDllSuffix * 4 >= RT_ELEMENTS(wszPath)) + { + supR3HardenedError(VINF_SUCCESS, false, + "supR3HardenedMonitor_LdrLoadDll: Name too long (system32): %.*ls\n", cwcOrgName, pawcOrgName); + SUP_DPRINTF(("supR3HardenedMonitor_LdrLoadDll: returns rcNt=%#x\n", STATUS_NAME_TOO_LONG)); + RtlRestoreLastWin32Error(dwSavedLastError); + return STATUS_NAME_TOO_LONG; + } + memcpy(wszPath, g_System32WinPath.UniStr.Buffer, cwc * sizeof(RTUTF16)); + wszPath[cwc++] = '\\'; + memcpy(&wszPath[cwc], pawcOrgName, cwcOrgName * sizeof(WCHAR)); + cwc += cwcOrgName; + if (!fNeedDllSuffix) + wszPath[cwc] = '\0'; + else + { + memcpy(&wszPath[cwc], L".dll", 5 * sizeof(WCHAR)); + cwc += 4; + } + fCheckIfLoaded = true; + } + + ResolvedName.Buffer = wszPath; + ResolvedName.Length = (USHORT)(cwc * sizeof(WCHAR)); + ResolvedName.MaximumLength = ResolvedName.Length + sizeof(WCHAR); + pName = &ResolvedName; + } + +#ifndef IN_SUP_R3_STATIC + /* + * Reject blacklisted DLLs based on input name. + */ + for (unsigned i = 0; g_aSupNtViBlacklistedDlls[i].psz != NULL; i++) + if (supR3HardenedIsFilenameMatchDll(pName, g_aSupNtViBlacklistedDlls[i].psz, g_aSupNtViBlacklistedDlls[i].cch)) + { + SUP_DPRINTF(("supR3HardenedMonitor_LdrLoadDll: Refusing to load blacklisted DLL: '%.*ls'\n", + pName->Length / sizeof(RTUTF16), pName->Buffer)); + RtlRestoreLastWin32Error(dwSavedLastError); + return STATUS_TOO_MANY_THREADS; + } +#endif + + bool fQuiet = false; + if (!fSkipValidation) + { + /* + * Try open the file. If this fails, never mind, just pass it on to + * the real API as we've replaced any searchable name with a full name + * and the real API can come up with a fitting status code for it. + */ + HANDLE hRootDir; + UNICODE_STRING NtPathUniStr; + int rc = RTNtPathFromWinUtf16Ex(&NtPathUniStr, &hRootDir, wszPath, RTSTR_MAX); + if (RT_FAILURE(rc)) + { + supR3HardenedError(rc, false, + "supR3HardenedMonitor_LdrLoadDll: RTNtPathFromWinUtf16Ex failed on '%ls': %Rrc\n", wszPath, rc); + SUP_DPRINTF(("supR3HardenedMonitor_LdrLoadDll: returns rcNt=%#x\n", STATUS_OBJECT_NAME_INVALID)); + RtlRestoreLastWin32Error(dwSavedLastError); + return STATUS_OBJECT_NAME_INVALID; + } + + HANDLE hFile = RTNT_INVALID_HANDLE_VALUE; + IO_STATUS_BLOCK Ios = RTNT_IO_STATUS_BLOCK_INITIALIZER; + OBJECT_ATTRIBUTES ObjAttr; + InitializeObjectAttributes(&ObjAttr, &NtPathUniStr, OBJ_CASE_INSENSITIVE, hRootDir, NULL /*pSecDesc*/); + + rcNt = NtCreateFile(&hFile, + FILE_READ_DATA | READ_CONTROL | 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)) + { + ULONG fAccess = 0; + ULONG fProtect = 0; + bool fCallRealApi = false; + rcNt = supR3HardenedScreenImage(hFile, true /*fImage*/, RT_VALID_PTR(pfFlags) && (*pfFlags & 0x2) /*fIgnoreArch*/, + &fAccess, &fProtect, &fCallRealApi, + "LdrLoadDll", false /*fAvoidWinVerifyTrust*/, &fQuiet); + NtClose(hFile); + if (!NT_SUCCESS(rcNt)) + { + if (!fQuiet) + { + if (pOrgName != pName) + supR3HardenedError(VINF_SUCCESS, false, "supR3HardenedMonitor_LdrLoadDll: rejecting '%ls': rcNt=%#x\n", + wszPath, rcNt); + else + supR3HardenedError(VINF_SUCCESS, false, "supR3HardenedMonitor_LdrLoadDll: rejecting '%ls' (%.*ls): rcNt=%#x\n", + wszPath, pOrgName->Length / sizeof(WCHAR), pOrgName->Buffer, rcNt); + SUP_DPRINTF(("supR3HardenedMonitor_LdrLoadDll: returns rcNt=%#x '%ls'\n", rcNt, wszPath)); + } + RtlRestoreLastWin32Error(dwSavedLastError); + return rcNt; + } + + supR3HardenedWinVerifyCacheProcessImportTodos(); + } + else + { + DWORD dwErr = RtlGetLastWin32Error(); + + /* + * Deal with special case where the caller (first case was MS LifeCam) + * is using LoadLibrary instead of GetModuleHandle to find a loaded DLL. + */ + NTSTATUS rcNtGetDll = STATUS_SUCCESS; + if ( fCheckIfLoaded + && ( rcNt == STATUS_OBJECT_NAME_NOT_FOUND + || rcNt == STATUS_OBJECT_PATH_NOT_FOUND)) + { + rcNtGetDll = LdrGetDllHandle(NULL /*DllPath*/, NULL /*pfFlags*/, pOrgName, phMod); + if (NT_SUCCESS(rcNtGetDll)) + { + RTNtPathFree(&NtPathUniStr, &hRootDir); + RtlRestoreLastWin32Error(dwSavedLastError); + return rcNtGetDll; + } + } + + SUP_DPRINTF(("supR3HardenedMonitor_LdrLoadDll: error opening '%ls': %u (NtPath=%.*ls; Input=%.*ls; rcNtGetDll=%#x\n", + wszPath, dwErr, NtPathUniStr.Length / sizeof(RTUTF16), NtPathUniStr.Buffer, + pOrgName->Length / sizeof(WCHAR), pOrgName->Buffer, rcNtGetDll)); + + RTNtPathFree(&NtPathUniStr, &hRootDir); + RtlRestoreLastWin32Error(dwSavedLastError); + SUP_DPRINTF(("supR3HardenedMonitor_LdrLoadDll: returns rcNt=%#x '%ls'\n", rcNt, wszPath)); + return rcNt; + } + RTNtPathFree(&NtPathUniStr, &hRootDir); + } + + /* + * Screened successfully enough. Call the real thing. + */ + if (!fQuiet) + { + if (pOrgName != pName) + SUP_DPRINTF(("supR3HardenedMonitor_LdrLoadDll: pName=%.*ls (Input=%.*ls, rcNtResolve=%#x) *pfFlags=%#x pwszSearchPath=%p:%ls [calling]\n", + (unsigned)pName->Length / sizeof(WCHAR), pName->Buffer, + (unsigned)pOrgName->Length / sizeof(WCHAR), pOrgName->Buffer, rcNtResolve, + pfFlags ? *pfFlags : UINT32_MAX, pwszSearchPath, + !((uintptr_t)pwszSearchPath & 1) && (uintptr_t)pwszSearchPath >= 0x2000U ? pwszSearchPath : L"<flags>")); + else + SUP_DPRINTF(("supR3HardenedMonitor_LdrLoadDll: pName=%.*ls (rcNtResolve=%#x) *pfFlags=%#x pwszSearchPath=%p:%ls [calling]\n", + (unsigned)pName->Length / sizeof(WCHAR), pName->Buffer, rcNtResolve, + pfFlags ? *pfFlags : UINT32_MAX, pwszSearchPath, + !((uintptr_t)pwszSearchPath & 1) && (uintptr_t)pwszSearchPath >= 0x2000U ? pwszSearchPath : L"<flags>")); + } + + RtlRestoreLastWin32Error(dwSavedLastError); + rcNt = g_pfnLdrLoadDllReal(pwszSearchPath, pfFlags, pName, phMod); + + /* + * Log the result and process pending WinVerifyTrust work if we can. + */ + dwSavedLastError = RtlGetLastWin32Error(); + + if (NT_SUCCESS(rcNt) && phMod) + SUP_DPRINTF(("supR3HardenedMonitor_LdrLoadDll: returns rcNt=%#x hMod=%p '%ls'\n", rcNt, *phMod, wszPath)); + else if (!NT_SUCCESS(rcNt) || !fQuiet) + SUP_DPRINTF(("supR3HardenedMonitor_LdrLoadDll: returns rcNt=%#x '%ls'\n", rcNt, wszPath)); + + supR3HardenedWinVerifyCacheProcessWvtTodos(); + + RtlRestoreLastWin32Error(dwSavedLastError); + + return rcNt; +} + + +/** + * DLL load and unload notification callback. + * + * This is a safety against our LdrLoadDll hook being replaced by protection + * software. Though, we prefer the LdrLoadDll hook to this one as it allows us + * to call WinVerifyTrust more freely. + * + * @param ulReason The reason we're called, see + * LDR_DLL_NOTIFICATION_REASON_XXX. + * @param pData Reason specific data. (Format is currently the same for + * both load and unload.) + * @param pvUser User parameter (ignored). + * + * @remarks Vista and later. + * @remarks The loader lock is held when we're called, at least on Windows 7. + */ +static VOID CALLBACK +supR3HardenedDllNotificationCallback(ULONG ulReason, PCLDR_DLL_NOTIFICATION_DATA pData, PVOID pvUser) RT_NOTHROW_DEF +{ + NOREF(pvUser); + + /* + * Screen the image on load. We will normally get a verification cache + * hit here because of the LdrLoadDll and NtCreateSection hooks, so it + * should be relatively cheap to recheck. In case our NtDll patches + * got re + * + * This ASSUMES that we get informed after the fact as indicated by the + * available documentation. + */ + if (ulReason == LDR_DLL_NOTIFICATION_REASON_LOADED) + { + SUP_DPRINTF(("supR3HardenedDllNotificationCallback: load %p LB %#010x %.*ls [fFlags=%#x]\n", + pData->Loaded.DllBase, pData->Loaded.SizeOfImage, + pData->Loaded.FullDllName->Length / sizeof(WCHAR), pData->Loaded.FullDllName->Buffer, + pData->Loaded.Flags)); + + /* Convert the windows path to an NT path and open it. */ + HANDLE hRootDir; + UNICODE_STRING NtPathUniStr; + int rc = RTNtPathFromWinUtf16Ex(&NtPathUniStr, &hRootDir, pData->Loaded.FullDllName->Buffer, + pData->Loaded.FullDllName->Length / sizeof(WCHAR)); + if (RT_FAILURE(rc)) + { + supR3HardenedFatal("supR3HardenedDllNotificationCallback: RTNtPathFromWinUtf16Ex failed on '%.*ls': %Rrc\n", + pData->Loaded.FullDllName->Length / sizeof(WCHAR), pData->Loaded.FullDllName->Buffer, rc); + return; + } + + HANDLE hFile = RTNT_INVALID_HANDLE_VALUE; + IO_STATUS_BLOCK Ios = RTNT_IO_STATUS_BLOCK_INITIALIZER; + OBJECT_ATTRIBUTES ObjAttr; + InitializeObjectAttributes(&ObjAttr, &NtPathUniStr, OBJ_CASE_INSENSITIVE, hRootDir, NULL /*pSecDesc*/); + + NTSTATUS rcNt = NtCreateFile(&hFile, + FILE_READ_DATA | READ_CONTROL | 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)) + { + supR3HardenedFatal("supR3HardenedDllNotificationCallback: NtCreateFile failed on '%.*ls' / '%.*ls': %#x\n", + pData->Loaded.FullDllName->Length / sizeof(WCHAR), pData->Loaded.FullDllName->Buffer, + NtPathUniStr.Length / sizeof(WCHAR), NtPathUniStr.Buffer, rcNt); + /* not reached */ + } + + /* Do the screening. */ + ULONG fAccess = 0; + ULONG fProtect = 0; + bool fCallRealApi = false; + bool fQuietFailure = false; + rcNt = supR3HardenedScreenImage(hFile, true /*fImage*/, true /*fIgnoreArch*/, &fAccess, &fProtect, &fCallRealApi, + "LdrLoadDll", true /*fAvoidWinVerifyTrust*/, &fQuietFailure); + NtClose(hFile); + if (!NT_SUCCESS(rcNt)) + { + supR3HardenedFatal("supR3HardenedDllNotificationCallback: supR3HardenedScreenImage failed on '%.*ls' / '%.*ls': %#x\n", + pData->Loaded.FullDllName->Length / sizeof(WCHAR), pData->Loaded.FullDllName->Buffer, + NtPathUniStr.Length / sizeof(WCHAR), NtPathUniStr.Buffer, rcNt); + /* not reached */ + } + RTNtPathFree(&NtPathUniStr, &hRootDir); + } + /* + * Log the unload call. + */ + else if (ulReason == LDR_DLL_NOTIFICATION_REASON_UNLOADED) + { + SUP_DPRINTF(("supR3HardenedDllNotificationCallback: Unload %p LB %#010x %.*ls [flags=%#x]\n", + pData->Unloaded.DllBase, pData->Unloaded.SizeOfImage, + pData->Unloaded.FullDllName->Length / sizeof(WCHAR), pData->Unloaded.FullDllName->Buffer, + pData->Unloaded.Flags)); + } + /* + * Just log things we don't know and then return without caching anything. + */ + else + { + static uint32_t s_cLogEntries = 0; + if (s_cLogEntries++ < 32) + SUP_DPRINTF(("supR3HardenedDllNotificationCallback: ulReason=%u pData=%p\n", ulReason, pData)); + return; + } + + /* + * Use this opportunity to make sure our NtDll patches are still in place, + * since they may be replaced by indecent protection software solutions. + */ + supR3HardenedWinReInstallHooks(false /*fFirstCall */); +} + + +/** + * Registers the DLL notification callback if it hasn't already been registered. + */ +static void supR3HardenedWinRegisterDllNotificationCallback(void) +{ + /* + * The notification API was added in Vista, so it's an optional (weak) import. + */ + if ( LdrRegisterDllNotification != NULL + && g_cDllNotificationRegistered <= 0 + && g_cDllNotificationRegistered > -32) + { + NTSTATUS rcNt = LdrRegisterDllNotification(0, supR3HardenedDllNotificationCallback, NULL, &g_pvDllNotificationCookie); + if (NT_SUCCESS(rcNt)) + { + SUP_DPRINTF(("Registered Dll notification callback with NTDLL.\n")); + g_cDllNotificationRegistered = 1; + } + else + { + supR3HardenedError(rcNt, false /*fFatal*/, "LdrRegisterDllNotification failed: %#x\n", rcNt); + g_cDllNotificationRegistered--; + } + } +} + + +/** + * Dummy replacement routine we use for passifying unwanted user APC + * callbacks during early process initialization. + * + * @sa supR3HardenedMonitor_KiUserApcDispatcher_C + */ +static VOID NTAPI supR3HardenedWinDummyApcRoutine(PVOID pvArg1, PVOID pvArg2, PVOID pvArg3) +{ + SUP_DPRINTF(("supR3HardenedWinDummyApcRoutine: pvArg1=%p pvArg2=%p pvArg3=%p\n", pvArg1, pvArg2, pvArg3)); + RT_NOREF(pvArg1, pvArg2, pvArg3); +} + + +/** + * This is called when ntdll!KiUserApcDispatcher is invoked (via + * supR3HardenedMonitor_KiUserApcDispatcher). + * + * The parent process hooks KiUserApcDispatcher before the guest starts + * executing. There should only be one APC request dispatched while the process + * is being initialized, and that's the one calling ntdll!LdrInitializeThunk. + * + * @returns Where to go to run the original code. + * @param pvApcArgs The APC dispatcher arguments. + */ +DECLASM(uintptr_t) supR3HardenedMonitor_KiUserApcDispatcher_C(void *pvApcArgs) +{ +#ifdef RT_ARCH_AMD64 + PCONTEXT pCtx = (PCONTEXT)pvApcArgs; + uintptr_t *ppfnRoutine = (uintptr_t *)&pCtx->P4Home; +#else + struct X86APCCTX + { + uintptr_t pfnRoutine; + uintptr_t pvCtx; + uintptr_t pvUser1; + uintptr_t pvUser2; + CONTEXT Ctx; + } *pCtx = (struct X86APCCTX *)pvApcArgs; + uintptr_t *ppfnRoutine = &pCtx->pfnRoutine; +#endif + uintptr_t pfnRoutine = *ppfnRoutine; + + if (g_enmSupR3HardenedMainState < SUPR3HARDENEDMAINSTATE_HARDENED_MAIN_CALLED) + { + if (pfnRoutine == g_pfnLdrInitializeThunk) /* Note! we could use this to detect thread creation too. */ + SUP_DPRINTF(("supR3HardenedMonitor_KiUserApcDispatcher_C: pfnRoutine=%p enmState=%d - okay\n", + pfnRoutine, g_enmSupR3HardenedMainState)); + else + { + *ppfnRoutine = (uintptr_t)supR3HardenedWinDummyApcRoutine; + SUP_DPRINTF(("supR3HardenedMonitor_KiUserApcDispatcher_C: pfnRoutine=%p enmState=%d -> supR3HardenedWinDummyApcRoutine\n", + pfnRoutine, g_enmSupR3HardenedMainState)); + } + } + return (uintptr_t)g_pfnKiUserApcDispatcherReal; +} + + +/** + * SUP_DPRINTF on pCtx, with lead-in text. + */ +static void supR3HardNtDprintCtx(PCONTEXT pCtx, const char *pszLeadIn) +{ +#ifdef RT_ARCH_AMD64 + SUP_DPRINTF(("%s\n" + " rax=%016RX64 rbx=%016RX64 rcx=%016RX64 rdx=%016RX64\n" + " rsi=%016RX64 rdi=%016RX64 r8 =%016RX64 r9 =%016RX64\n" + " r10=%016RX64 r11=%016RX64 r12=%016RX64 r13=%016RX64\n" + " r14=%016RX64 r15=%016RX64 P1=%016RX64 P2=%016RX64\n" + " rip=%016RX64 rsp=%016RX64 rbp=%016RX64 ctxflags=%08x\n" + " cs=%04x ss=%04x ds=%04x es=%04x fs=%04x gs=%04x eflags=%08x mxcrx=%08x\n" + " P3=%016RX64 P4=%016RX64 P5=%016RX64 P6=%016RX64\n" + " dr0=%016RX64 dr1=%016RX64 dr2=%016RX64 dr3=%016RX64\n" + " dr6=%016RX64 dr7=%016RX64 vcr=%016RX64 dcr=%016RX64\n" + " lbt=%016RX64 lbf=%016RX64 lxt=%016RX64 lxf=%016RX64\n" + , + pszLeadIn, + pCtx->Rax, pCtx->Rbx, pCtx->Rcx, pCtx->Rdx, + pCtx->Rsi, pCtx->Rdi, pCtx->R8, pCtx->R9, + pCtx->R10, pCtx->R11, pCtx->R12, pCtx->R13, + pCtx->R14, pCtx->R15, pCtx->P1Home, pCtx->P2Home, + pCtx->Rip, pCtx->Rsp, pCtx->Rbp, pCtx->ContextFlags, + pCtx->SegCs, pCtx->SegSs, pCtx->SegDs, pCtx->SegEs, pCtx->SegFs, pCtx->SegGs, pCtx->EFlags, pCtx->MxCsr, + pCtx->P3Home, pCtx->P4Home, pCtx->P5Home, pCtx->P6Home, + pCtx->Dr0, pCtx->Dr1, pCtx->Dr2, pCtx->Dr3, + pCtx->Dr6, pCtx->Dr7, pCtx->VectorControl, pCtx->DebugControl, + pCtx->LastBranchToRip, pCtx->LastBranchFromRip, pCtx->LastExceptionToRip, pCtx->LastExceptionFromRip )); +#elif defined(RT_ARCH_X86) + SUP_DPRINTF(("%s\n" + " eax=%08RX32 ebx=%08RX32 ecx=%08RX32 edx=%08RX32 esi=%08rx64 edi=%08RX32\n" + " eip=%08RX32 esp=%08RX32 ebp=%08RX32 eflags=%08RX32\n" + " cs=%04RX16 ds=%04RX16 es=%04RX16 fs=%04RX16 gs=%04RX16\n" + " dr0=%08RX32 dr1=%08RX32 dr2=%08RX32 dr3=%08RX32 dr6=%08RX32 dr7=%08RX32\n", + pszLeadIn, + pCtx->Eax, pCtx->Ebx, pCtx->Ecx, pCtx->Edx, pCtx->Esi, pCtx->Edi, + pCtx->Eip, pCtx->Esp, pCtx->Ebp, pCtx->EFlags, + pCtx->SegCs, pCtx->SegDs, pCtx->SegEs, pCtx->SegFs, pCtx->SegGs, + pCtx->Dr0, pCtx->Dr1, pCtx->Dr2, pCtx->Dr3, pCtx->Dr6, pCtx->Dr7)); +#else +# error "Unsupported arch." +#endif + RT_NOREF(pCtx, pszLeadIn); +} + + +#ifndef VBOX_WITHOUT_HARDENDED_XCPT_LOGGING +/** + * This is called when ntdll!KiUserExceptionDispatcher is invoked (via + * supR3HardenedMonitor_KiUserExceptionDispatcher). + * + * For 64-bit processes there is a return and two parameters on the stack. + * + * @returns Where to go to run the original code. + * @param pXcptRec The exception record. + * @param pCtx The exception context. + */ +DECLASM(uintptr_t) supR3HardenedMonitor_KiUserExceptionDispatcher_C(PEXCEPTION_RECORD pXcptRec, PCONTEXT pCtx) +{ + /* + * Ignore the guard page violation. + */ + if (pXcptRec->ExceptionCode == STATUS_GUARD_PAGE_VIOLATION) + return (uintptr_t)g_pfnKiUserExceptionDispatcherReal; + + /* + * Log the exception and context. + */ + char szLeadIn[384]; + if (pXcptRec->NumberParameters == 0) + RTStrPrintf(szLeadIn, sizeof(szLeadIn), "KiUserExceptionDispatcher: %#x @ %p (flags=%#x)", + pXcptRec->ExceptionCode, pXcptRec->ExceptionAddress, pXcptRec->ExceptionFlags); + else if (pXcptRec->NumberParameters == 1) + RTStrPrintf(szLeadIn, sizeof(szLeadIn), "KiUserExceptionDispatcher: %#x (%p) @ %p (flags=%#x)", + pXcptRec->ExceptionCode, pXcptRec->ExceptionInformation[0], + pXcptRec->ExceptionAddress, pXcptRec->ExceptionFlags); + else if (pXcptRec->NumberParameters == 2) + RTStrPrintf(szLeadIn, sizeof(szLeadIn), "KiUserExceptionDispatcher: %#x (%p, %p) @ %p (flags=%#x)", + pXcptRec->ExceptionCode, pXcptRec->ExceptionInformation[0], pXcptRec->ExceptionInformation[1], + pXcptRec->ExceptionAddress, pXcptRec->ExceptionFlags); + else if (pXcptRec->NumberParameters == 3) + RTStrPrintf(szLeadIn, sizeof(szLeadIn), "KiUserExceptionDispatcher: %#x (%p, %p, %p) @ %p (flags=%#x)", + pXcptRec->ExceptionCode, pXcptRec->ExceptionInformation[0], pXcptRec->ExceptionInformation[1], + pXcptRec->ExceptionInformation[2], pXcptRec->ExceptionAddress, pXcptRec->ExceptionFlags); + else + RTStrPrintf(szLeadIn, sizeof(szLeadIn), "KiUserExceptionDispatcher: %#x (#%u: %p, %p, %p, %p, %p, %p, %p, %p, ...) @ %p (flags=%#x)", + pXcptRec->ExceptionCode, pXcptRec->NumberParameters, + pXcptRec->ExceptionInformation[0], pXcptRec->ExceptionInformation[1], + pXcptRec->ExceptionInformation[2], pXcptRec->ExceptionInformation[3], + pXcptRec->ExceptionInformation[4], pXcptRec->ExceptionInformation[5], + pXcptRec->ExceptionInformation[6], pXcptRec->ExceptionInformation[7], + pXcptRec->ExceptionAddress, pXcptRec->ExceptionFlags); + supR3HardNtDprintCtx(pCtx, szLeadIn); + + return (uintptr_t)g_pfnKiUserExceptionDispatcherReal; +} +#endif /* !VBOX_WITHOUT_HARDENDED_XCPT_LOGGING */ + + +static void supR3HardenedWinHookFailed(const char *pszWhich, uint8_t const *pbPrologue) +{ + supR3HardenedFatalMsg("supR3HardenedWinInstallHooks", kSupInitOp_Misc, VERR_NO_MEMORY, + "Failed to install %s monitor: %x %x %x %x %x %x %x %x %x %x %x %x %x %x %x %x\n " +#ifdef RT_ARCH_X86 + "(It is also possible you are running 32-bit VirtualBox under 64-bit windows.)\n" +#endif + , + pszWhich, + pbPrologue[0], pbPrologue[1], pbPrologue[2], pbPrologue[3], + pbPrologue[4], pbPrologue[5], pbPrologue[6], pbPrologue[7], + pbPrologue[8], pbPrologue[9], pbPrologue[10], pbPrologue[11], + pbPrologue[12], pbPrologue[13], pbPrologue[14], pbPrologue[15]); +} + + +/** + * IPRT thread that waits for the parent process to terminate and reacts by + * exiting the current process. + * + * @returns VINF_SUCCESS + * @param hSelf The current thread. Ignored. + * @param pvUser The handle of the parent process. + */ +static DECLCALLBACK(int) supR3HardenedWinParentWatcherThread(RTTHREAD hSelf, void *pvUser) +{ + HANDLE hProcWait = (HANDLE)pvUser; + NOREF(hSelf); + + /* + * Wait for the parent to terminate. + */ + NTSTATUS rcNt; + for (;;) + { + rcNt = NtWaitForSingleObject(hProcWait, TRUE /*Alertable*/, NULL /*pTimeout*/); + if ( rcNt == STATUS_WAIT_0 + || rcNt == STATUS_ABANDONED_WAIT_0) + break; + if ( rcNt != STATUS_TIMEOUT + && rcNt != STATUS_USER_APC + && rcNt != STATUS_ALERTED) + supR3HardenedFatal("NtWaitForSingleObject returned %#x\n", rcNt); + } + + /* + * Proxy the termination code of the child, if it exited already. + */ + PROCESS_BASIC_INFORMATION BasicInfo; + NTSTATUS rcNt2 = NtQueryInformationProcess(hProcWait, ProcessBasicInformation, &BasicInfo, sizeof(BasicInfo), NULL); + if ( !NT_SUCCESS(rcNt2) + || BasicInfo.ExitStatus == STATUS_PENDING) + BasicInfo.ExitStatus = RTEXITCODE_FAILURE; + + NtClose(hProcWait); + SUP_DPRINTF(("supR3HardenedWinParentWatcherThread: Quitting: ExitCode=%#x rcNt=%#x\n", BasicInfo.ExitStatus, rcNt)); + suplibHardenedExit((RTEXITCODE)BasicInfo.ExitStatus); + /* not reached */ +} + + +/** + * Creates the parent watcher thread that will make sure this process exits when + * the parent does. + * + * This is a necessary evil to make VBoxNetDhcp and VBoxNetNat termination from + * Main work without too much new magic. It also makes Ctrl-C or similar work + * in on the hardened processes in the windows console. + * + * @param hVBoxRT The VBoxRT.dll handle. We use RTThreadCreate to + * spawn the thread to avoid duplicating thread + * creation and thread naming code from IPRT. + */ +DECLHIDDEN(void) supR3HardenedWinCreateParentWatcherThread(HMODULE hVBoxRT) +{ + /* + * Resolve runtime methods that we need. + */ + PFNRTTHREADCREATE pfnRTThreadCreate = (PFNRTTHREADCREATE)GetProcAddress(hVBoxRT, "RTThreadCreate"); + SUPR3HARDENED_ASSERT(pfnRTThreadCreate != NULL); + + /* + * Find the parent process ID. + */ + PROCESS_BASIC_INFORMATION BasicInfo; + NTSTATUS rcNt = NtQueryInformationProcess(NtCurrentProcess(), ProcessBasicInformation, &BasicInfo, sizeof(BasicInfo), NULL); + if (!NT_SUCCESS(rcNt)) + supR3HardenedFatal("supR3HardenedWinCreateParentWatcherThread: NtQueryInformationProcess failed: %#x\n", rcNt); + + /* + * Open the parent process for waiting and exitcode query. + */ + OBJECT_ATTRIBUTES ObjAttr; + InitializeObjectAttributes(&ObjAttr, NULL, 0, NULL /*hRootDir*/, NULL /*pSecDesc*/); + + CLIENT_ID ClientId; + ClientId.UniqueProcess = (HANDLE)BasicInfo.InheritedFromUniqueProcessId; + ClientId.UniqueThread = NULL; + + HANDLE hParent; + rcNt = NtOpenProcess(&hParent, SYNCHRONIZE | PROCESS_QUERY_INFORMATION, &ObjAttr, &ClientId); + if (!NT_SUCCESS(rcNt)) + supR3HardenedFatalMsg("supR3HardenedWinCreateParentWatcherThread", kSupInitOp_Misc, VERR_GENERAL_FAILURE, + "NtOpenProcess(%p.0) failed: %#x\n", ClientId.UniqueProcess, rcNt); + + /* + * Create the thread that should do the waiting. + */ + int rc = pfnRTThreadCreate(NULL, supR3HardenedWinParentWatcherThread, hParent, _64K /* stack */, + RTTHREADTYPE_DEFAULT, 0 /*fFlags*/, "ParentWatcher"); + if (RT_FAILURE(rc)) + supR3HardenedFatal("supR3HardenedWinCreateParentWatcherThread: RTThreadCreate failed: %Rrc\n", rc); +} + + +/** + * Checks if the calling thread is the only one in the process. + * + * @returns true if we're positive we're alone, false if not. + */ +static bool supR3HardenedWinAmIAlone(void) RT_NOTHROW_DEF +{ + ULONG fAmIAlone = 0; + ULONG cbIgn = 0; + NTSTATUS rcNt = NtQueryInformationThread(NtCurrentThread(), ThreadAmILastThread, &fAmIAlone, sizeof(fAmIAlone), &cbIgn); + Assert(NT_SUCCESS(rcNt)); + return NT_SUCCESS(rcNt) && fAmIAlone != 0; +} + + +/** + * Simplify NtProtectVirtualMemory interface. + * + * Modifies protection for the current process. Caller must know the current + * protection as it's not returned. + * + * @returns NT status code. + * @param pvMem The memory to change protection for. + * @param cbMem The amount of memory to change. + * @param fNewProt The new protection. + */ +static NTSTATUS supR3HardenedWinProtectMemory(PVOID pvMem, SIZE_T cbMem, ULONG fNewProt) RT_NOTHROW_DEF +{ + ULONG fOldProt = 0; + return NtProtectVirtualMemory(NtCurrentProcess(), &pvMem, &cbMem, fNewProt, &fOldProt); +} + + +/** + * Installs or reinstalls the NTDLL patches. + */ +static void supR3HardenedWinReInstallHooks(bool fFirstCall) RT_NOTHROW_DEF +{ + struct + { + size_t cbPatch; + uint8_t const *pabPatch; + uint8_t **ppbApi; + const char *pszName; + } const s_aPatches[] = + { + { sizeof(g_abNtCreateSectionPatch), g_abNtCreateSectionPatch, &g_pbNtCreateSection, "NtCreateSection" }, + { sizeof(g_abLdrLoadDllPatch), g_abLdrLoadDllPatch, &g_pbLdrLoadDll, "LdrLoadDll" }, + { sizeof(g_abKiUserApcDispatcherPatch), g_abKiUserApcDispatcherPatch, &g_pbKiUserApcDispatcher, "KiUserApcDispatcher" }, +#ifndef VBOX_WITHOUT_HARDENDED_XCPT_LOGGING + { sizeof(g_abKiUserExceptionDispatcherPatch), g_abKiUserExceptionDispatcherPatch, &g_pbKiUserExceptionDispatcher, "KiUserExceptionDispatcher" }, +#endif + }; + + ULONG fAmIAlone = ~(ULONG)0; + + for (uint32_t i = 0; i < RT_ELEMENTS(s_aPatches); i++) + { + uint8_t *pbApi = *s_aPatches[i].ppbApi; + if (memcmp(pbApi, s_aPatches[i].pabPatch, s_aPatches[i].cbPatch) != 0) + { + /* + * Log the incident if it's not the initial call. + */ + static uint32_t volatile s_cTimes = 0; + if (!fFirstCall && s_cTimes < 128) + { + s_cTimes++; + SUP_DPRINTF(("supR3HardenedWinReInstallHooks: Reinstalling %s (%p: %.*Rhxs).\n", + s_aPatches[i].pszName, pbApi, s_aPatches[i].cbPatch, pbApi)); + } + + Assert(s_aPatches[i].cbPatch >= 4); + + SUPR3HARDENED_ASSERT_NT_SUCCESS(supR3HardenedWinProtectMemory(pbApi, s_aPatches[i].cbPatch, PAGE_EXECUTE_READWRITE)); + + /* + * If we're alone, just memcpy the patch in. + */ + + if (fAmIAlone == ~(ULONG)0) + fAmIAlone = supR3HardenedWinAmIAlone(); + if (fAmIAlone) + memcpy(pbApi, s_aPatches[i].pabPatch, s_aPatches[i].cbPatch); + else + { + /* + * Not alone. Start by injecting a JMP $-2, then waste some + * CPU cycles to get the other threads a good chance of getting + * out of the code before we replace it. + */ + RTUINT32U uJmpDollarMinus; + uJmpDollarMinus.au8[0] = 0xeb; + uJmpDollarMinus.au8[1] = 0xfe; + uJmpDollarMinus.au8[2] = pbApi[2]; + uJmpDollarMinus.au8[3] = pbApi[3]; + ASMAtomicXchgU32((uint32_t volatile *)pbApi, uJmpDollarMinus.u); + + NtYieldExecution(); + NtYieldExecution(); + + /* Copy in the tail bytes of the patch, then xchg the jmp $-2. */ + if (s_aPatches[i].cbPatch > 4) + memcpy(&pbApi[4], &s_aPatches[i].pabPatch[4], s_aPatches[i].cbPatch - 4); + ASMAtomicXchgU32((uint32_t volatile *)pbApi, *(uint32_t *)s_aPatches[i].pabPatch); + } + + SUPR3HARDENED_ASSERT_NT_SUCCESS(supR3HardenedWinProtectMemory(pbApi, s_aPatches[i].cbPatch, PAGE_EXECUTE_READ)); + } + } +} + + +/** + * Install hooks for intercepting calls dealing with mapping shared libraries + * into the process. + * + * This allows us to prevent undesirable shared libraries from being loaded. + * + * @remarks We assume we're alone in this process, so no seralizing trickery is + * necessary when installing the patch. + * + * @remarks We would normally just copy the prologue sequence somewhere and add + * a jump back at the end of it. But because we wish to avoid + * allocating executable memory, we need to have preprepared assembly + * "copies". This makes the non-system call patching a little tedious + * and inflexible. + */ +static void supR3HardenedWinInstallHooks(void) +{ + NTSTATUS rcNt; + + /* + * Disable hard error popups so we can quietly refuse images to be loaded. + */ + ULONG fHardErr = 0; + rcNt = NtQueryInformationProcess(NtCurrentProcess(), ProcessDefaultHardErrorMode, &fHardErr, sizeof(fHardErr), NULL); + if (!NT_SUCCESS(rcNt)) + supR3HardenedFatalMsg("supR3HardenedWinInstallHooks", kSupInitOp_Misc, VERR_GENERAL_FAILURE, + "NtQueryInformationProcess/ProcessDefaultHardErrorMode failed: %#x\n", rcNt); + if (fHardErr & PROCESS_HARDERR_CRITICAL_ERROR) + { + fHardErr &= ~PROCESS_HARDERR_CRITICAL_ERROR; + rcNt = NtSetInformationProcess(NtCurrentProcess(), ProcessDefaultHardErrorMode, &fHardErr, sizeof(fHardErr)); + if (!NT_SUCCESS(rcNt)) + supR3HardenedFatalMsg("supR3HardenedWinInstallHooks", kSupInitOp_Misc, VERR_GENERAL_FAILURE, + "NtSetInformationProcess/ProcessDefaultHardErrorMode failed: %#x\n", rcNt); + } + + /* + * Locate the routines first so we can allocate memory that's near enough. + */ + PFNRT pfnNtCreateSection = supR3HardenedWinGetRealDllSymbol("ntdll.dll", "NtCreateSection"); + SUPR3HARDENED_ASSERT(pfnNtCreateSection != NULL); + //SUPR3HARDENED_ASSERT(pfnNtCreateSection == (FARPROC)NtCreateSection); + + PFNRT pfnLdrLoadDll = supR3HardenedWinGetRealDllSymbol("ntdll.dll", "LdrLoadDll"); + SUPR3HARDENED_ASSERT(pfnLdrLoadDll != NULL); + //SUPR3HARDENED_ASSERT(pfnLdrLoadDll == (FARPROC)LdrLoadDll); + + PFNRT pfnKiUserApcDispatcher = supR3HardenedWinGetRealDllSymbol("ntdll.dll", "KiUserApcDispatcher"); + SUPR3HARDENED_ASSERT(pfnKiUserApcDispatcher != NULL); + g_pfnLdrInitializeThunk = (uintptr_t)supR3HardenedWinGetRealDllSymbol("ntdll.dll", "LdrInitializeThunk"); + SUPR3HARDENED_ASSERT(g_pfnLdrInitializeThunk != NULL); + +#ifndef VBOX_WITHOUT_HARDENDED_XCPT_LOGGING + PFNRT pfnKiUserExceptionDispatcher = supR3HardenedWinGetRealDllSymbol("ntdll.dll", "KiUserExceptionDispatcher"); + SUPR3HARDENED_ASSERT(pfnKiUserExceptionDispatcher != NULL); +#endif + + /* + * Exec page setup & management. + */ + uint32_t offExecPage = 0; + memset(g_abSupHardReadWriteExecPage, 0xcc, PAGE_SIZE); + + /* + * Hook #1 - NtCreateSection. + * Purpose: Validate everything that can be mapped into the process before + * it's mapped and we still have a file handle to work with. + */ + uint8_t * const pbNtCreateSection = (uint8_t *)(uintptr_t)pfnNtCreateSection; + g_pbNtCreateSection = pbNtCreateSection; + memcpy(g_abNtCreateSectionPatch, pbNtCreateSection, sizeof(g_abNtCreateSectionPatch)); + + g_pfnNtCreateSectionReal = NtCreateSection; /* our direct syscall */ + +#ifdef RT_ARCH_AMD64 + /* + * Patch 64-bit hosts. + */ + /* Pattern #1: XP64/W2K3-64 thru Windows 8.1 + 0:000> u ntdll!NtCreateSection + ntdll!NtCreateSection: + 00000000`779f1750 4c8bd1 mov r10,rcx + 00000000`779f1753 b847000000 mov eax,47h + 00000000`779f1758 0f05 syscall + 00000000`779f175a c3 ret + 00000000`779f175b 0f1f440000 nop dword ptr [rax+rax] + The variant is the value loaded into eax: W2K3=??, Vista=47h?, W7=47h, W80=48h, W81=49h */ + + /* Assemble the patch. */ + g_abNtCreateSectionPatch[0] = 0x48; /* mov rax, qword */ + g_abNtCreateSectionPatch[1] = 0xb8; + *(uint64_t *)&g_abNtCreateSectionPatch[2] = (uint64_t)supR3HardenedMonitor_NtCreateSection; + g_abNtCreateSectionPatch[10] = 0xff; /* jmp rax */ + g_abNtCreateSectionPatch[11] = 0xe0; + +#else + /* + * Patch 32-bit hosts. + */ + /* Pattern #1: XP thru Windows 7 + kd> u ntdll!NtCreateSection + ntdll!NtCreateSection: + 7c90d160 b832000000 mov eax,32h + 7c90d165 ba0003fe7f mov edx,offset SharedUserData!SystemCallStub (7ffe0300) + 7c90d16a ff12 call dword ptr [edx] + 7c90d16c c21c00 ret 1Ch + 7c90d16f 90 nop + The variable bit is the value loaded into eax: XP=32h, W2K3=34h, Vista=4bh, W7=54h + + Pattern #2: Windows 8.1 + 0:000:x86> u ntdll_6a0f0000!NtCreateSection + ntdll_6a0f0000!NtCreateSection: + 6a15eabc b854010000 mov eax,154h + 6a15eac1 e803000000 call ntdll_6a0f0000!NtCreateSection+0xd (6a15eac9) + 6a15eac6 c21c00 ret 1Ch + 6a15eac9 8bd4 mov edx,esp + 6a15eacb 0f34 sysenter + 6a15eacd c3 ret + The variable bit is the value loaded into eax: W81=154h */ + + /* Assemble the patch. */ + g_abNtCreateSectionPatch[0] = 0xe9; /* jmp rel32 */ + *(uint32_t *)&g_abNtCreateSectionPatch[1] = (uintptr_t)supR3HardenedMonitor_NtCreateSection + - (uintptr_t)&pbNtCreateSection[1+4]; + +#endif + + /* + * Hook #2 - LdrLoadDll + * Purpose: (a) Enforce LdrLoadDll search path constraints, and (b) pre-validate + * DLLs so we can avoid calling WinVerifyTrust from the first hook, + * and thus avoiding messing up the loader data on some installations. + * + * This differs from the above function in that is no a system call and + * we're at the mercy of the compiler. + */ + uint8_t * const pbLdrLoadDll = (uint8_t *)(uintptr_t)pfnLdrLoadDll; + g_pbLdrLoadDll = pbLdrLoadDll; + memcpy(g_abLdrLoadDllPatch, pbLdrLoadDll, sizeof(g_abLdrLoadDllPatch)); + + DISSTATE Dis; + uint32_t cbInstr; + uint32_t offJmpBack = 0; + +#ifdef RT_ARCH_AMD64 + /* + * Patch 64-bit hosts. + */ + /* Just use the disassembler to skip 12 bytes or more. */ + while (offJmpBack < 12) + { + cbInstr = 1; + int rc = DISInstr(pbLdrLoadDll + offJmpBack, DISCPUMODE_64BIT, &Dis, &cbInstr); + if ( RT_FAILURE(rc) + || (Dis.pCurInstr->fOpType & (DISOPTYPE_CONTROLFLOW)) + || (Dis.ModRM.Bits.Mod == 0 && Dis.ModRM.Bits.Rm == 5 /* wrt RIP */) ) + supR3HardenedWinHookFailed("LdrLoadDll", pbLdrLoadDll); + offJmpBack += cbInstr; + } + + /* Assemble the code for resuming the call.*/ + *(PFNRT *)&g_pfnLdrLoadDllReal = (PFNRT)(uintptr_t)&g_abSupHardReadWriteExecPage[offExecPage]; + + memcpy(&g_abSupHardReadWriteExecPage[offExecPage], pbLdrLoadDll, offJmpBack); + offExecPage += offJmpBack; + + g_abSupHardReadWriteExecPage[offExecPage++] = 0xff; /* jmp qword [$+8 wrt RIP] */ + g_abSupHardReadWriteExecPage[offExecPage++] = 0x25; + *(uint32_t *)&g_abSupHardReadWriteExecPage[offExecPage] = RT_ALIGN_32(offExecPage + 4, 8) - (offExecPage + 4); + offExecPage = RT_ALIGN_32(offExecPage + 4, 8); + *(uint64_t *)&g_abSupHardReadWriteExecPage[offExecPage] = (uintptr_t)&pbLdrLoadDll[offJmpBack]; + offExecPage = RT_ALIGN_32(offExecPage + 8, 16); + + /* Assemble the LdrLoadDll patch. */ + Assert(offJmpBack >= 12); + g_abLdrLoadDllPatch[0] = 0x48; /* mov rax, qword */ + g_abLdrLoadDllPatch[1] = 0xb8; + *(uint64_t *)&g_abLdrLoadDllPatch[2] = (uint64_t)supR3HardenedMonitor_LdrLoadDll; + g_abLdrLoadDllPatch[10] = 0xff; /* jmp rax */ + g_abLdrLoadDllPatch[11] = 0xe0; + +#else + /* + * Patch 32-bit hosts. + */ + /* Just use the disassembler to skip 5 bytes or more. */ + while (offJmpBack < 5) + { + cbInstr = 1; + int rc = DISInstr(pbLdrLoadDll + offJmpBack, DISCPUMODE_32BIT, &Dis, &cbInstr); + if ( RT_FAILURE(rc) + || (Dis.pCurInstr->fOpType & (DISOPTYPE_CONTROLFLOW)) ) + supR3HardenedWinHookFailed("LdrLoadDll", pbLdrLoadDll); + offJmpBack += cbInstr; + } + + /* Assemble the code for resuming the call.*/ + *(PFNRT *)&g_pfnLdrLoadDllReal = (PFNRT)(uintptr_t)&g_abSupHardReadWriteExecPage[offExecPage]; + + memcpy(&g_abSupHardReadWriteExecPage[offExecPage], pbLdrLoadDll, offJmpBack); + offExecPage += offJmpBack; + + g_abSupHardReadWriteExecPage[offExecPage++] = 0xe9; /* jmp rel32 */ + *(uint32_t *)&g_abSupHardReadWriteExecPage[offExecPage] = (uintptr_t)&pbLdrLoadDll[offJmpBack] + - (uintptr_t)&g_abSupHardReadWriteExecPage[offExecPage + 4]; + offExecPage = RT_ALIGN_32(offExecPage + 4, 16); + + /* Assemble the LdrLoadDll patch. */ + memcpy(g_abLdrLoadDllPatch, pbLdrLoadDll, sizeof(g_abLdrLoadDllPatch)); + Assert(offJmpBack >= 5); + g_abLdrLoadDllPatch[0] = 0xe9; + *(uint32_t *)&g_abLdrLoadDllPatch[1] = (uintptr_t)supR3HardenedMonitor_LdrLoadDll - (uintptr_t)&pbLdrLoadDll[1+4]; +#endif + + /* + * Hook #3 - KiUserApcDispatcher + * Purpose: Prevent user APC to memory we (or our parent) has freed from + * crashing the process. Also ensures no code injection via user + * APC during process init given the way we're vetting the APCs. + * + * This differs from the first function in that is no a system call and + * we're at the mercy of the handwritten assembly. + * + * Note! We depend on all waits up past the patching to be non-altertable, + * otherwise an APC might slip by us. + */ + uint8_t * const pbKiUserApcDispatcher = (uint8_t *)(uintptr_t)pfnKiUserApcDispatcher; + g_pbKiUserApcDispatcher = pbKiUserApcDispatcher; + memcpy(g_abKiUserApcDispatcherPatch, pbKiUserApcDispatcher, sizeof(g_abKiUserApcDispatcherPatch)); + +#ifdef RT_ARCH_AMD64 + /* + * Patch 64-bit hosts. + */ + /* Just use the disassembler to skip 12 bytes or more. */ + offJmpBack = 0; + while (offJmpBack < 12) + { + cbInstr = 1; + int rc = DISInstr(pbKiUserApcDispatcher + offJmpBack, DISCPUMODE_64BIT, &Dis, &cbInstr); + if ( RT_FAILURE(rc) + || (Dis.pCurInstr->fOpType & (DISOPTYPE_CONTROLFLOW)) + || (Dis.ModRM.Bits.Mod == 0 && Dis.ModRM.Bits.Rm == 5 /* wrt RIP */) ) + supR3HardenedWinHookFailed("KiUserApcDispatcher", pbKiUserApcDispatcher); + offJmpBack += cbInstr; + } + + /* Assemble the code for resuming the call.*/ + *(PFNRT *)&g_pfnKiUserApcDispatcherReal = (PFNRT)(uintptr_t)&g_abSupHardReadWriteExecPage[offExecPage]; + + memcpy(&g_abSupHardReadWriteExecPage[offExecPage], pbKiUserApcDispatcher, offJmpBack); + offExecPage += offJmpBack; + + g_abSupHardReadWriteExecPage[offExecPage++] = 0xff; /* jmp qword [$+8 wrt RIP] */ + g_abSupHardReadWriteExecPage[offExecPage++] = 0x25; + *(uint32_t *)&g_abSupHardReadWriteExecPage[offExecPage] = RT_ALIGN_32(offExecPage + 4, 8) - (offExecPage + 4); + offExecPage = RT_ALIGN_32(offExecPage + 4, 8); + *(uint64_t *)&g_abSupHardReadWriteExecPage[offExecPage] = (uintptr_t)&pbKiUserApcDispatcher[offJmpBack]; + offExecPage = RT_ALIGN_32(offExecPage + 8, 16); + + /* Assemble the KiUserApcDispatcher patch. */ + Assert(offJmpBack >= 12); + g_abKiUserApcDispatcherPatch[0] = 0x48; /* mov rax, qword */ + g_abKiUserApcDispatcherPatch[1] = 0xb8; + *(uint64_t *)&g_abKiUserApcDispatcherPatch[2] = (uint64_t)supR3HardenedMonitor_KiUserApcDispatcher; + g_abKiUserApcDispatcherPatch[10] = 0xff; /* jmp rax */ + g_abKiUserApcDispatcherPatch[11] = 0xe0; + +#else + /* + * Patch 32-bit hosts. + */ + /* Just use the disassembler to skip 5 bytes or more. */ + offJmpBack = 0; + while (offJmpBack < 5) + { + cbInstr = 1; + int rc = DISInstr(pbKiUserApcDispatcher + offJmpBack, DISCPUMODE_32BIT, &Dis, &cbInstr); + if ( RT_FAILURE(rc) + || (Dis.pCurInstr->fOpType & (DISOPTYPE_CONTROLFLOW)) ) + supR3HardenedWinHookFailed("KiUserApcDispatcher", pbKiUserApcDispatcher); + offJmpBack += cbInstr; + } + + /* Assemble the code for resuming the call.*/ + *(PFNRT *)&g_pfnKiUserApcDispatcherReal = (PFNRT)(uintptr_t)&g_abSupHardReadWriteExecPage[offExecPage]; + + memcpy(&g_abSupHardReadWriteExecPage[offExecPage], pbKiUserApcDispatcher, offJmpBack); + offExecPage += offJmpBack; + + g_abSupHardReadWriteExecPage[offExecPage++] = 0xe9; /* jmp rel32 */ + *(uint32_t *)&g_abSupHardReadWriteExecPage[offExecPage] = (uintptr_t)&pbKiUserApcDispatcher[offJmpBack] + - (uintptr_t)&g_abSupHardReadWriteExecPage[offExecPage + 4]; + offExecPage = RT_ALIGN_32(offExecPage + 4, 16); + + /* Assemble the KiUserApcDispatcher patch. */ + memcpy(g_abKiUserApcDispatcherPatch, pbKiUserApcDispatcher, sizeof(g_abKiUserApcDispatcherPatch)); + Assert(offJmpBack >= 5); + g_abKiUserApcDispatcherPatch[0] = 0xe9; + *(uint32_t *)&g_abKiUserApcDispatcherPatch[1] = (uintptr_t)supR3HardenedMonitor_KiUserApcDispatcher - (uintptr_t)&pbKiUserApcDispatcher[1+4]; +#endif + +#ifndef VBOX_WITHOUT_HARDENDED_XCPT_LOGGING + /* + * Hook #4 - KiUserExceptionDispatcher + * Purpose: Logging crashes. + * + * This differs from the first function in that is no a system call and + * we're at the mercy of the handwritten assembly. This is not mandatory, + * so we ignore failures here. + */ + uint8_t * const pbKiUserExceptionDispatcher = (uint8_t *)(uintptr_t)pfnKiUserExceptionDispatcher; + g_pbKiUserExceptionDispatcher = pbKiUserExceptionDispatcher; + memcpy(g_abKiUserExceptionDispatcherPatch, pbKiUserExceptionDispatcher, sizeof(g_abKiUserExceptionDispatcherPatch)); + +# ifdef RT_ARCH_AMD64 + /* + * Patch 64-bit hosts. + * + * Assume the following sequence and replacing the loaded Wow64PrepareForException + * function pointer with our callback: + * cld + * mov rax, Wow64PrepareForException ; Wow64PrepareForException(PCONTEXT, PEXCEPTION_RECORD) + * test rax, rax + * jz skip_wow64_callout + * <do_callout_thru_rax> + * (We're not a WOW64 process, so the callout should normally never happen.) + */ + if ( pbKiUserExceptionDispatcher[ 0] == 0xfc /* CLD */ + && pbKiUserExceptionDispatcher[ 1] == 0x48 /* MOV RAX, symbol wrt rip */ + && pbKiUserExceptionDispatcher[ 2] == 0x8b + && pbKiUserExceptionDispatcher[ 3] == 0x05 + && pbKiUserExceptionDispatcher[ 8] == 0x48 /* TEST RAX, RAX */ + && pbKiUserExceptionDispatcher[ 9] == 0x85 + && pbKiUserExceptionDispatcher[10] == 0xc0 + && pbKiUserExceptionDispatcher[11] == 0x74) + { + /* Assemble the KiUserExceptionDispatcher patch. */ + g_abKiUserExceptionDispatcherPatch[1] = 0x48; /* MOV RAX, supR3HardenedMonitor_KiUserExceptionDispatcher */ + g_abKiUserExceptionDispatcherPatch[2] = 0xb8; + *(uint64_t *)&g_abKiUserExceptionDispatcherPatch[3] = (uint64_t)supR3HardenedMonitor_KiUserExceptionDispatcher; + g_abKiUserExceptionDispatcherPatch[11] = 0x90; /* NOP (was JZ) */ + g_abKiUserExceptionDispatcherPatch[12] = 0x90; /* NOP (was DISP8 of JZ) */ + } + else + SUP_DPRINTF(("supR3HardenedWinInstallHooks: failed to patch KiUserExceptionDispatcher (%.20Rhxs)\n", + pbKiUserExceptionDispatcher)); +# else + /* + * Patch 32-bit hosts. + */ + /* Just use the disassembler to skip 5 bytes or more. */ + offJmpBack = 0; + while (offJmpBack < 5) + { + cbInstr = 1; + int rc = DISInstr(pbKiUserExceptionDispatcher + offJmpBack, DISCPUMODE_32BIT, &Dis, &cbInstr); + if ( RT_FAILURE(rc) + || (Dis.pCurInstr->fOpType & (DISOPTYPE_CONTROLFLOW)) ) + { + SUP_DPRINTF(("supR3HardenedWinInstallHooks: failed to patch KiUserExceptionDispatcher (off %#x in %.20Rhxs)\n", + offJmpBack, pbKiUserExceptionDispatcher)); + break; + } + offJmpBack += cbInstr; + } + if (offJmpBack >= 5) + { + /* Assemble the code for resuming the call.*/ + *(PFNRT *)&g_pfnKiUserExceptionDispatcherReal = (PFNRT)(uintptr_t)&g_abSupHardReadWriteExecPage[offExecPage]; + + memcpy(&g_abSupHardReadWriteExecPage[offExecPage], pbKiUserExceptionDispatcher, offJmpBack); + offExecPage += offJmpBack; + + g_abSupHardReadWriteExecPage[offExecPage++] = 0xe9; /* jmp rel32 */ + *(uint32_t *)&g_abSupHardReadWriteExecPage[offExecPage] = (uintptr_t)&pbKiUserExceptionDispatcher[offJmpBack] + - (uintptr_t)&g_abSupHardReadWriteExecPage[offExecPage + 4]; + offExecPage = RT_ALIGN_32(offExecPage + 4, 16); + + /* Assemble the KiUserExceptionDispatcher patch. */ + memcpy(g_abKiUserExceptionDispatcherPatch, pbKiUserExceptionDispatcher, sizeof(g_abKiUserExceptionDispatcherPatch)); + Assert(offJmpBack >= 5); + g_abKiUserExceptionDispatcherPatch[0] = 0xe9; + *(uint32_t *)&g_abKiUserExceptionDispatcherPatch[1] = (uintptr_t)supR3HardenedMonitor_KiUserExceptionDispatcher - (uintptr_t)&pbKiUserExceptionDispatcher[1+4]; + } +# endif +#endif /* !VBOX_WITHOUT_HARDENDED_XCPT_LOGGING */ + + /* + * Seal the rwx page. + */ + SUPR3HARDENED_ASSERT_NT_SUCCESS(supR3HardenedWinProtectMemory(g_abSupHardReadWriteExecPage, PAGE_SIZE, PAGE_EXECUTE_READ)); + + /* + * Install the patches. + */ + supR3HardenedWinReInstallHooks(true /*fFirstCall*/); +} + + + + + + +/* + * + * T h r e a d c r e a t i o n c o n t r o l + * T h r e a d c r e a t i o n c o n t r o l + * T h r e a d c r e a t i o n c o n t r o l + * + */ + + +/** + * Common code used for child and parent to make new threads exit immediately. + * + * This patches the LdrInitializeThunk code to call NtTerminateThread with + * STATUS_SUCCESS instead of doing the NTDLL initialization. + * + * @returns VBox status code. + * @param hProcess The process to do this to. + * @param pvLdrInitThunk The address of the LdrInitializeThunk code to + * override. + * @param pvNtTerminateThread The address of the NtTerminateThread function in + * the NTDLL instance we're patching. (Must be +/- + * 2GB from the thunk code.) + * @param pabBackup Where to back up the original instruction bytes + * at pvLdrInitThunk. + * @param cbBackup The size of the backup area. Must be 16 bytes. + * @param pErrInfo Where to return extended error information. + * Optional. + */ +static int supR3HardNtDisableThreadCreationEx(HANDLE hProcess, void *pvLdrInitThunk, void *pvNtTerminateThread, + uint8_t *pabBackup, size_t cbBackup, PRTERRINFO pErrInfo) +{ + SUP_DPRINTF(("supR3HardNtDisableThreadCreation: pvLdrInitThunk=%p pvNtTerminateThread=%p\n", pvLdrInitThunk, pvNtTerminateThread)); + SUPR3HARDENED_ASSERT(cbBackup == 16); + SUPR3HARDENED_ASSERT(RT_ABS((intptr_t)pvLdrInitThunk - (intptr_t)pvNtTerminateThread) < 16*_1M); + + /* + * Back up the thunk code. + */ + SIZE_T cbIgnored; + NTSTATUS rcNt = NtReadVirtualMemory(hProcess, pvLdrInitThunk, pabBackup, cbBackup, &cbIgnored); + if (!NT_SUCCESS(rcNt)) + return RTErrInfoSetF(pErrInfo, VERR_GENERAL_FAILURE, + "supR3HardNtDisableThreadCreation: NtReadVirtualMemory/LdrInitializeThunk failed: %#x", rcNt); + + /* + * Cook up replacement code that calls NtTerminateThread. + */ + uint8_t abReplacement[16]; + memcpy(abReplacement, pabBackup, sizeof(abReplacement)); + +#ifdef RT_ARCH_AMD64 + abReplacement[0] = 0x31; /* xor ecx, ecx */ + abReplacement[1] = 0xc9; + abReplacement[2] = 0x31; /* xor edx, edx */ + abReplacement[3] = 0xd2; + abReplacement[4] = 0xe8; /* call near NtTerminateThread */ + *(int32_t *)&abReplacement[5] = (int32_t)((uintptr_t)pvNtTerminateThread - ((uintptr_t)pvLdrInitThunk + 9)); + abReplacement[9] = 0xcc; /* int3 */ +#elif defined(RT_ARCH_X86) + abReplacement[0] = 0x6a; /* push 0 */ + abReplacement[1] = 0x00; + abReplacement[2] = 0x6a; /* push 0 */ + abReplacement[3] = 0x00; + abReplacement[4] = 0xe8; /* call near NtTerminateThread */ + *(int32_t *)&abReplacement[5] = (int32_t)((uintptr_t)pvNtTerminateThread - ((uintptr_t)pvLdrInitThunk + 9)); + abReplacement[9] = 0xcc; /* int3 */ +#else +# error "Unsupported arch." +#endif + + /* + * Install the replacment code. + */ + PVOID pvProt = pvLdrInitThunk; + SIZE_T cbProt = cbBackup; + ULONG fOldProt = 0; + rcNt = NtProtectVirtualMemory(hProcess, &pvProt, &cbProt, PAGE_EXECUTE_READWRITE, &fOldProt); + if (!NT_SUCCESS(rcNt)) + return RTErrInfoSetF(pErrInfo, VERR_GENERAL_FAILURE, + "supR3HardNtDisableThreadCreationEx: NtProtectVirtualMemory/LdrInitializeThunk failed: %#x", rcNt); + + rcNt = NtWriteVirtualMemory(hProcess, pvLdrInitThunk, abReplacement, sizeof(abReplacement), &cbIgnored); + if (!NT_SUCCESS(rcNt)) + return RTErrInfoSetF(pErrInfo, VERR_GENERAL_FAILURE, + "supR3HardNtDisableThreadCreationEx: NtWriteVirtualMemory/LdrInitializeThunk failed: %#x", rcNt); + + pvProt = pvLdrInitThunk; + cbProt = cbBackup; + rcNt = NtProtectVirtualMemory(hProcess, &pvProt, &cbProt, fOldProt, &fOldProt); + if (!NT_SUCCESS(rcNt)) + return RTErrInfoSetF(pErrInfo, VERR_GENERAL_FAILURE, + "supR3HardNtDisableThreadCreationEx: NtProtectVirtualMemory/LdrInitializeThunk/2 failed: %#x", rcNt); + + return VINF_SUCCESS; +} + + +/** + * Undo the effects of supR3HardNtDisableThreadCreationEx. + * + * @returns VBox status code. + * @param hProcess The process to do this to. + * @param pvLdrInitThunk The address of the LdrInitializeThunk code to + * override. + * @param pabBackup Where to back up the original instruction bytes + * at pvLdrInitThunk. + * @param cbBackup The size of the backup area. Must be 16 bytes. + * @param pErrInfo Where to return extended error information. + * Optional. + */ +static int supR3HardNtEnableThreadCreationEx(HANDLE hProcess, void *pvLdrInitThunk, uint8_t const *pabBackup, size_t cbBackup, + PRTERRINFO pErrInfo) +{ + SUP_DPRINTF(("supR3HardNtEnableThreadCreationEx:\n")); + SUPR3HARDENED_ASSERT(cbBackup == 16); + + PVOID pvProt = pvLdrInitThunk; + SIZE_T cbProt = cbBackup; + ULONG fOldProt = 0; + NTSTATUS rcNt = NtProtectVirtualMemory(hProcess, &pvProt, &cbProt, PAGE_EXECUTE_READWRITE, &fOldProt); + if (!NT_SUCCESS(rcNt)) + return RTErrInfoSetF(pErrInfo, VERR_GENERAL_FAILURE, + "supR3HardNtEnableThreadCreationEx: NtProtectVirtualMemory/LdrInitializeThunk failed: %#x", rcNt); + + SIZE_T cbIgnored; + rcNt = NtWriteVirtualMemory(hProcess, pvLdrInitThunk, pabBackup, cbBackup, &cbIgnored); + if (!NT_SUCCESS(rcNt)) + return RTErrInfoSetF(pErrInfo, VERR_GENERAL_FAILURE, + "supR3HardNtEnableThreadCreationEx: NtWriteVirtualMemory/LdrInitializeThunk[restore] failed: %#x", + rcNt); + + pvProt = pvLdrInitThunk; + cbProt = cbBackup; + rcNt = NtProtectVirtualMemory(hProcess, &pvProt, &cbProt, fOldProt, &fOldProt); + if (!NT_SUCCESS(rcNt)) + return RTErrInfoSetF(pErrInfo, VERR_GENERAL_FAILURE, + "supR3HardNtEnableThreadCreationEx: NtProtectVirtualMemory/LdrInitializeThunk[restore] failed: %#x", + rcNt); + + return VINF_SUCCESS; +} + + +/** + * Disable thread creation for the current process. + * + * @remarks Doesn't really disables it, just makes the threads exit immediately + * without executing any real code. + */ +static void supR3HardenedWinDisableThreadCreation(void) +{ + /* Cannot use the imported NtTerminateThread as it's pointing to our own + syscall assembly code. */ + static PFNRT s_pfnNtTerminateThread = NULL; + if (s_pfnNtTerminateThread == NULL) + s_pfnNtTerminateThread = supR3HardenedWinGetRealDllSymbol("ntdll.dll", "NtTerminateThread"); + SUPR3HARDENED_ASSERT(s_pfnNtTerminateThread); + + int rc = supR3HardNtDisableThreadCreationEx(NtCurrentProcess(), + (void *)(uintptr_t)&LdrInitializeThunk, + (void *)(uintptr_t)s_pfnNtTerminateThread, + g_abLdrInitThunkSelfBackup, sizeof(g_abLdrInitThunkSelfBackup), + NULL /* pErrInfo*/); + g_fSupInitThunkSelfPatched = RT_SUCCESS(rc); +} + + +/** + * Undoes the effects of supR3HardenedWinDisableThreadCreation. + */ +DECLHIDDEN(void) supR3HardenedWinEnableThreadCreation(void) +{ + if (g_fSupInitThunkSelfPatched) + { + int rc = supR3HardNtEnableThreadCreationEx(NtCurrentProcess(), + (void *)(uintptr_t)&LdrInitializeThunk, + g_abLdrInitThunkSelfBackup, sizeof(g_abLdrInitThunkSelfBackup), + RTErrInfoInitStatic(&g_ErrInfoStatic)); + if (RT_FAILURE(rc)) + supR3HardenedError(rc, true /*fFatal*/, "%s", g_ErrInfoStatic.szMsg); + g_fSupInitThunkSelfPatched = false; + } +} + + + + +/* + * + * R e s p a w n + * R e s p a w n + * R e s p a w n + * + */ + + +/** + * Gets the SID of the user associated with the process. + * + * @returns @c true if we've got a login SID, @c false if not. + * @param pSidUser Where to return the user SID. + * @param cbSidUser The size of the user SID buffer. + * @param pSidLogin Where to return the login SID. + * @param cbSidLogin The size of the login SID buffer. + */ +static bool supR3HardNtChildGetUserAndLogSids(PSID pSidUser, ULONG cbSidUser, PSID pSidLogin, ULONG cbSidLogin) +{ + HANDLE hToken; + SUPR3HARDENED_ASSERT_NT_SUCCESS(NtOpenProcessToken(NtCurrentProcess(), TOKEN_QUERY, &hToken)); + union + { + TOKEN_USER UserInfo; + TOKEN_GROUPS Groups; + uint8_t abPadding[4096]; + } uBuf; + ULONG cbRet = 0; + SUPR3HARDENED_ASSERT_NT_SUCCESS(NtQueryInformationToken(hToken, TokenUser, &uBuf, sizeof(uBuf), &cbRet)); + SUPR3HARDENED_ASSERT_NT_SUCCESS(RtlCopySid(cbSidUser, pSidUser, uBuf.UserInfo.User.Sid)); + + bool fLoginSid = false; + NTSTATUS rcNt = NtQueryInformationToken(hToken, TokenLogonSid, &uBuf, sizeof(uBuf), &cbRet); + if (NT_SUCCESS(rcNt)) + { + for (DWORD i = 0; i < uBuf.Groups.GroupCount; i++) + if ((uBuf.Groups.Groups[i].Attributes & SE_GROUP_LOGON_ID) == SE_GROUP_LOGON_ID) + { + SUPR3HARDENED_ASSERT_NT_SUCCESS(RtlCopySid(cbSidLogin, pSidLogin, uBuf.Groups.Groups[i].Sid)); + fLoginSid = true; + break; + } + } + + SUPR3HARDENED_ASSERT_NT_SUCCESS(NtClose(hToken)); + + return fLoginSid; +} + + +/** + * Build security attributes for the process or the primary thread (@a fProcess) + * + * Process DACLs can be bypassed using the SeDebugPrivilege (generally available + * to admins, i.e. normal windows users), or by taking ownership and/or + * modifying the DACL. However, it restricts + * + * @param pSecAttrs Where to return the security attributes. + * @param pCleanup Cleanup record. + * @param fProcess Set if it's for the process, clear if it's for + * the primary thread. + */ +static void supR3HardNtChildInitSecAttrs(PSECURITY_ATTRIBUTES pSecAttrs, PMYSECURITYCLEANUP pCleanup, bool fProcess) +{ + /* + * Safe return values. + */ + suplibHardenedMemSet(pCleanup, 0, sizeof(*pCleanup)); + + pSecAttrs->nLength = sizeof(*pSecAttrs); + pSecAttrs->bInheritHandle = FALSE; + pSecAttrs->lpSecurityDescriptor = NULL; + +/** @todo This isn't at all complete, just sketches... */ + + /* + * Create an ACL detailing the access of the above groups. + */ + SUPR3HARDENED_ASSERT_NT_SUCCESS(RtlCreateAcl(&pCleanup->Acl.AclHdr, sizeof(pCleanup->Acl), ACL_REVISION)); + + ULONG fDeny = DELETE | WRITE_DAC | WRITE_OWNER; + ULONG fAllow = SYNCHRONIZE | READ_CONTROL; + ULONG fAllowLogin = SYNCHRONIZE | READ_CONTROL; + if (fProcess) + { + fDeny |= PROCESS_CREATE_THREAD | PROCESS_SET_SESSIONID | PROCESS_VM_OPERATION | PROCESS_VM_WRITE + | PROCESS_CREATE_PROCESS | PROCESS_DUP_HANDLE | PROCESS_SET_QUOTA + | PROCESS_SET_INFORMATION | PROCESS_SUSPEND_RESUME; + fAllow |= PROCESS_TERMINATE | PROCESS_VM_READ | PROCESS_QUERY_INFORMATION; + fAllowLogin |= PROCESS_TERMINATE | PROCESS_VM_READ | PROCESS_QUERY_INFORMATION; + if (g_uNtVerCombined >= SUP_MAKE_NT_VER_SIMPLE(6, 0)) /* Introduced in Vista. */ + { + fAllow |= PROCESS_QUERY_LIMITED_INFORMATION; + fAllowLogin |= PROCESS_QUERY_LIMITED_INFORMATION; + } + if (g_uNtVerCombined >= SUP_MAKE_NT_VER_SIMPLE(6, 3)) /* Introduced in Windows 8.1. */ + fAllow |= PROCESS_SET_LIMITED_INFORMATION; + } + else + { + fDeny |= THREAD_SUSPEND_RESUME | THREAD_SET_CONTEXT | THREAD_SET_INFORMATION | THREAD_SET_THREAD_TOKEN + | THREAD_IMPERSONATE | THREAD_DIRECT_IMPERSONATION; + fAllow |= THREAD_GET_CONTEXT | THREAD_QUERY_INFORMATION; + fAllowLogin |= THREAD_GET_CONTEXT | THREAD_QUERY_INFORMATION; + if (g_uNtVerCombined >= SUP_MAKE_NT_VER_SIMPLE(6, 0)) /* Introduced in Vista. */ + { + fAllow |= THREAD_QUERY_LIMITED_INFORMATION | THREAD_SET_LIMITED_INFORMATION; + fAllowLogin |= THREAD_QUERY_LIMITED_INFORMATION; + } + + } + fDeny |= ~fAllow & (SPECIFIC_RIGHTS_ALL | STANDARD_RIGHTS_ALL); + + /* Deny everyone access to bad bits. */ +#if 1 + SID_IDENTIFIER_AUTHORITY SIDAuthWorld = SECURITY_WORLD_SID_AUTHORITY; + SUPR3HARDENED_ASSERT_NT_SUCCESS(RtlInitializeSid(&pCleanup->Everyone.Sid, &SIDAuthWorld, 1)); + *RtlSubAuthoritySid(&pCleanup->Everyone.Sid, 0) = SECURITY_WORLD_RID; + SUPR3HARDENED_ASSERT_NT_SUCCESS(RtlAddAccessDeniedAce(&pCleanup->Acl.AclHdr, ACL_REVISION, + fDeny, &pCleanup->Everyone.Sid)); +#endif + +#if 0 + /* Grant some access to the owner - doesn't work. */ + SID_IDENTIFIER_AUTHORITY SIDAuthCreator = SECURITY_CREATOR_SID_AUTHORITY; + SUPR3HARDENED_ASSERT_NT_SUCCESS(RtlInitializeSid(&pCleanup->Owner.Sid, &SIDAuthCreator, 1)); + *RtlSubAuthoritySid(&pCleanup->Owner.Sid, 0) = SECURITY_CREATOR_OWNER_RID; + + SUPR3HARDENED_ASSERT_NT_SUCCESS(RtlAddAccessDeniedAce(&pCleanup->Acl.AclHdr, ACL_REVISION, + fDeny, &pCleanup->Owner.Sid)); + SUPR3HARDENED_ASSERT_NT_SUCCESS(RtlAddAccessAllowedAce(&pCleanup->Acl.AclHdr, ACL_REVISION, + fAllow, &pCleanup->Owner.Sid)); +#endif + +#if 1 + bool fHasLoginSid = supR3HardNtChildGetUserAndLogSids(&pCleanup->User.Sid, sizeof(pCleanup->User), + &pCleanup->Login.Sid, sizeof(pCleanup->Login)); + +# if 1 + /* Grant minimal access to the user. */ + SUPR3HARDENED_ASSERT_NT_SUCCESS(RtlAddAccessDeniedAce(&pCleanup->Acl.AclHdr, ACL_REVISION, + fDeny, &pCleanup->User.Sid)); + SUPR3HARDENED_ASSERT_NT_SUCCESS(RtlAddAccessAllowedAce(&pCleanup->Acl.AclHdr, ACL_REVISION, + fAllow, &pCleanup->User.Sid)); +# endif + +# if 1 + /* Grant very limited access to the login sid. */ + if (fHasLoginSid) + { + SUPR3HARDENED_ASSERT_NT_SUCCESS(RtlAddAccessAllowedAce(&pCleanup->Acl.AclHdr, ACL_REVISION, + fAllowLogin, &pCleanup->Login.Sid)); + } +# endif + +#endif + + /* + * Create a security descriptor with the above ACL. + */ + PSECURITY_DESCRIPTOR pSecDesc = (PSECURITY_DESCRIPTOR)RTMemAllocZ(SECURITY_DESCRIPTOR_MIN_LENGTH); + pCleanup->pSecDesc = pSecDesc; + + SUPR3HARDENED_ASSERT_NT_SUCCESS(RtlCreateSecurityDescriptor(pSecDesc, SECURITY_DESCRIPTOR_REVISION)); + SUPR3HARDENED_ASSERT_NT_SUCCESS(RtlSetDaclSecurityDescriptor(pSecDesc, TRUE /*fDaclPresent*/, &pCleanup->Acl.AclHdr, + FALSE /*fDaclDefaulted*/)); + pSecAttrs->lpSecurityDescriptor = pSecDesc; +} + + +/** + * Predicate function which tests whether @a ch is a argument separator + * character. + * + * @returns True/false. + * @param ch The character to examine. + */ +DECLINLINE(bool) suplibCommandLineIsArgSeparator(int ch) +{ + return ch == ' ' + || ch == '\t' + || ch == '\n' + || ch == '\r'; +} + + +/** + * Construct the new command line. + * + * Since argc/argv are both derived from GetCommandLineW (see + * suplibHardenedWindowsMain), we skip the argument by argument UTF-8 -> UTF-16 + * conversion and quoting by going to the original source. + * + * The executable name, though, is replaced in case it's not a fullly + * qualified path. + * + * The re-spawn indicator is added immediately after the executable name + * so that we don't get tripped up missing close quote chars in the last + * argument. + * + * @returns Pointer to a command line string (heap). + * @param pString Unicode string structure to initialize to the + * command line. Optional. + * @param iWhich Which respawn we're to check for, 1 being the first + * one, and 2 the second and final. + */ +static PRTUTF16 supR3HardNtChildConstructCmdLine(PUNICODE_STRING pString, int iWhich) +{ + SUPR3HARDENED_ASSERT(iWhich == 1 || iWhich == 2); + + /* + * Get the command line and skip the executable name. + */ + PUNICODE_STRING pCmdLineStr = &NtCurrentPeb()->ProcessParameters->CommandLine; + PCRTUTF16 pawcArgs = pCmdLineStr->Buffer; + uint32_t cwcArgs = pCmdLineStr->Length / sizeof(WCHAR); + + /* Skip leading space (shouldn't be any, but whatever). */ + while (cwcArgs > 0 && suplibCommandLineIsArgSeparator(*pawcArgs) ) + cwcArgs--, pawcArgs++; + SUPR3HARDENED_ASSERT(cwcArgs > 0 && *pawcArgs != '\0'); + + /* Walk to the end of it. */ + int fQuoted = false; + do + { + if (*pawcArgs == '"') + { + fQuoted = !fQuoted; + cwcArgs--; pawcArgs++; + } + else if (*pawcArgs != '\\' || (pawcArgs[1] != '\\' && pawcArgs[1] != '"')) + cwcArgs--, pawcArgs++; + else + { + unsigned cSlashes = 0; + do + { + cSlashes++; + cwcArgs--; + pawcArgs++; + } + while (cwcArgs > 0 && *pawcArgs == '\\'); + if (cwcArgs > 0 && *pawcArgs == '"' && (cSlashes & 1)) + cwcArgs--, pawcArgs++; /* odd number of slashes == escaped quote */ + } + } while (cwcArgs > 0 && (fQuoted || !suplibCommandLineIsArgSeparator(*pawcArgs))); + + /* Skip trailing spaces. */ + while (cwcArgs > 0 && suplibCommandLineIsArgSeparator(*pawcArgs)) + cwcArgs--, pawcArgs++; + + /* + * Allocate a new buffer. + */ + AssertCompile(sizeof(SUPR3_RESPAWN_1_ARG0) == sizeof(SUPR3_RESPAWN_2_ARG0)); + size_t cwcCmdLine = (sizeof(SUPR3_RESPAWN_1_ARG0) - 1) / sizeof(SUPR3_RESPAWN_1_ARG0[0]) /* Respawn exe name. */ + + !!cwcArgs + cwcArgs; /* if arguments present, add space + arguments. */ + if (cwcCmdLine * sizeof(WCHAR) >= 0xfff0) + supR3HardenedFatalMsg("supR3HardNtChildConstructCmdLine", kSupInitOp_Misc, VERR_OUT_OF_RANGE, + "Command line is too long (%u chars)!", cwcCmdLine); + + PRTUTF16 pwszCmdLine = (PRTUTF16)RTMemAlloc((cwcCmdLine + 1) * sizeof(RTUTF16)); + SUPR3HARDENED_ASSERT(pwszCmdLine != NULL); + + /* + * Construct the new command line. + */ + PRTUTF16 pwszDst = pwszCmdLine; + for (const char *pszSrc = iWhich == 1 ? SUPR3_RESPAWN_1_ARG0 : SUPR3_RESPAWN_2_ARG0; *pszSrc; pszSrc++) + *pwszDst++ = *pszSrc; + + if (cwcArgs) + { + *pwszDst++ = ' '; + suplibHardenedMemCopy(pwszDst, pawcArgs, cwcArgs * sizeof(RTUTF16)); + pwszDst += cwcArgs; + } + + *pwszDst = '\0'; + SUPR3HARDENED_ASSERT((uintptr_t)(pwszDst - pwszCmdLine) == cwcCmdLine); + + if (pString) + { + pString->Buffer = pwszCmdLine; + pString->Length = (USHORT)(cwcCmdLine * sizeof(WCHAR)); + pString->MaximumLength = pString->Length + sizeof(WCHAR); + } + return pwszCmdLine; +} + + +/** + * Terminates the child process. + * + * @param hProcess The process handle. + * @param pszWhere Who's having child rasing troubles. + * @param rc The status code to report. + * @param pszFormat The message format string. + * @param ... Message format arguments. + */ +static void supR3HardenedWinKillChild(HANDLE hProcess, const char *pszWhere, int rc, const char *pszFormat, ...) +{ + /* + * Terminate the process ASAP and display error. + */ + NtTerminateProcess(hProcess, RTEXITCODE_FAILURE); + + va_list va; + va_start(va, pszFormat); + supR3HardenedErrorV(rc, false /*fFatal*/, pszFormat, va); + va_end(va); + + /* + * Wait for the process to really go away. + */ + PROCESS_BASIC_INFORMATION BasicInfo; + NTSTATUS rcNtExit = NtQueryInformationProcess(hProcess, ProcessBasicInformation, &BasicInfo, sizeof(BasicInfo), NULL); + bool fExitOk = NT_SUCCESS(rcNtExit) && BasicInfo.ExitStatus != STATUS_PENDING; + if (!fExitOk) + { + NTSTATUS rcNtWait; + uint64_t uMsTsStart = supR3HardenedWinGetMilliTS(); + do + { + NtTerminateProcess(hProcess, DBG_TERMINATE_PROCESS); + + LARGE_INTEGER Timeout; + Timeout.QuadPart = -20000000; /* 2 second */ + rcNtWait = NtWaitForSingleObject(hProcess, TRUE /*Alertable*/, &Timeout); + + rcNtExit = NtQueryInformationProcess(hProcess, ProcessBasicInformation, &BasicInfo, sizeof(BasicInfo), NULL); + fExitOk = NT_SUCCESS(rcNtExit) && BasicInfo.ExitStatus != STATUS_PENDING; + } while ( !fExitOk + && ( rcNtWait == STATUS_TIMEOUT + || rcNtWait == STATUS_USER_APC + || rcNtWait == STATUS_ALERTED) + && supR3HardenedWinGetMilliTS() - uMsTsStart < 60 * 1000); + if (fExitOk) + supR3HardenedError(rc, false /*fFatal*/, + "NtDuplicateObject failed and we failed to kill child: rc=%u (%#x) rcNtWait=%#x hProcess=%p\n", + rc, rc, rcNtWait, hProcess); + } + + /* + * Final error message. + */ + va_start(va, pszFormat); + supR3HardenedFatalMsgV(pszWhere, kSupInitOp_Misc, rc, pszFormat, va); + /* not reached */ +} + + +/** + * Checks the child process when hEvtParent is signalled. + * + * This will read the request data from the child and check it against expected + * request. If an error is signalled, we'll raise it and make sure the child + * terminates before terminating the calling process. + * + * @param pThis The child process data structure. + * @param enmExpectedRequest The expected child request. + * @param pszWhat What we're waiting for. + */ +static void supR3HardNtChildProcessRequest(PSUPR3HARDNTCHILD pThis, SUPR3WINCHILDREQ enmExpectedRequest, const char *pszWhat) +{ + /* + * Read the process parameters from the child. + */ + uintptr_t uChildAddr = (uintptr_t)pThis->Peb.ImageBaseAddress + + ((uintptr_t)&g_ProcParams - (uintptr_t)NtCurrentPeb()->ImageBaseAddress); + SIZE_T cbIgnored = 0; + RT_ZERO(pThis->ProcParams); + NTSTATUS rcNt = NtReadVirtualMemory(pThis->hProcess, (PVOID)uChildAddr, + &pThis->ProcParams, sizeof(pThis->ProcParams), &cbIgnored); + if (!NT_SUCCESS(rcNt)) + supR3HardenedWinKillChild(pThis, "supR3HardNtChildProcessRequest", rcNt, + "NtReadVirtualMemory(,%p,) failed reading child process status: %#x\n", uChildAddr, rcNt); + + /* + * Is it the expected request? + */ + if (pThis->ProcParams.enmRequest == enmExpectedRequest) + return; + + /* + * No, not the expected request. If it's an error request, tell the child + * to terminate itself, otherwise we'll have to terminate it. + */ + pThis->ProcParams.szErrorMsg[sizeof(pThis->ProcParams.szErrorMsg) - 1] = '\0'; + pThis->ProcParams.szWhere[sizeof(pThis->ProcParams.szWhere) - 1] = '\0'; + SUP_DPRINTF(("supR3HardenedWinCheckChild: enmRequest=%d rc=%d enmWhat=%d %s: %s\n", + pThis->ProcParams.enmRequest, pThis->ProcParams.rc, pThis->ProcParams.enmWhat, + pThis->ProcParams.szWhere, pThis->ProcParams.szErrorMsg)); + + if (pThis->ProcParams.enmRequest != kSupR3WinChildReq_Error) + supR3HardenedWinKillChild(pThis, "supR3HardenedWinCheckChild", VERR_INVALID_PARAMETER, + "Unexpected child request #%d. Was expecting #%d (%s).\n", + pThis->ProcParams.enmRequest, enmExpectedRequest, pszWhat); + + rcNt = NtSetEvent(pThis->hEvtChild, NULL); + if (!NT_SUCCESS(rcNt)) + supR3HardenedWinKillChild(pThis, "supR3HardNtChildProcessRequest", rcNt, "NtSetEvent failed: %#x\n", rcNt); + + /* Wait for it to terminate. */ + LARGE_INTEGER Timeout; + Timeout.QuadPart = -50000000; /* 5 seconds */ + rcNt = NtWaitForSingleObject(pThis->hProcess, FALSE /*Alertable*/, &Timeout); + if (rcNt != STATUS_WAIT_0) + { + SUP_DPRINTF(("supR3HardNtChildProcessRequest: Child is taking too long to quit (rcWait=%#x), killing it...\n", rcNt)); + NtTerminateProcess(pThis->hProcess, DBG_TERMINATE_PROCESS); + } + + /* + * Report the error in the same way as it occured in the guest. + */ + if (pThis->ProcParams.enmWhat == kSupInitOp_Invalid) + supR3HardenedFatalMsg("supR3HardenedWinCheckChild", kSupInitOp_Misc, pThis->ProcParams.rc, + "%s", pThis->ProcParams.szErrorMsg); + else + supR3HardenedFatalMsg(pThis->ProcParams.szWhere, pThis->ProcParams.enmWhat, pThis->ProcParams.rc, + "%s", pThis->ProcParams.szErrorMsg); +} + + +/** + * Waits for the child to make a certain request or terminate. + * + * The stub process will also wait on it's parent to terminate. + * This call will only return if the child made the expected request. + * + * @param pThis The child process data structure. + * @param enmExpectedRequest The child request to wait for. + * @param cMsTimeout The number of milliseconds to wait (at least). + * @param pszWhat What we're waiting for. + */ +static void supR3HardNtChildWaitFor(PSUPR3HARDNTCHILD pThis, SUPR3WINCHILDREQ enmExpectedRequest, RTMSINTERVAL cMsTimeout, + const char *pszWhat) +{ + /* + * The wait loop. + * Will return when the expected request arrives. + * Will break out when one of the processes terminates. + */ + NTSTATUS rcNtWait; + LARGE_INTEGER Timeout; + uint64_t uMsTsStart = supR3HardenedWinGetMilliTS(); + uint64_t cMsElapsed = 0; + for (;;) + { + /* + * Assemble handles to wait for. + */ + ULONG cHandles = 1; + HANDLE ahHandles[3]; + ahHandles[0] = pThis->hProcess; + if (pThis->hEvtParent) + ahHandles[cHandles++] = pThis->hEvtParent; + if (pThis->hParent) + ahHandles[cHandles++] = pThis->hParent; + + /* + * Do the waiting according to the callers wishes. + */ + if ( enmExpectedRequest == kSupR3WinChildReq_End + || cMsTimeout == RT_INDEFINITE_WAIT) + rcNtWait = NtWaitForMultipleObjects(cHandles, &ahHandles[0], WaitAnyObject, TRUE /*Alertable*/, NULL /*Timeout*/); + else + { + Timeout.QuadPart = -(int64_t)(cMsTimeout - cMsElapsed) * 10000; + rcNtWait = NtWaitForMultipleObjects(cHandles, &ahHandles[0], WaitAnyObject, TRUE /*Alertable*/, &Timeout); + } + + /* + * Process child request. + */ + if (rcNtWait == STATUS_WAIT_0 + 1 && pThis->hEvtParent != NULL) + { + supR3HardNtChildProcessRequest(pThis, enmExpectedRequest, pszWhat); + SUP_DPRINTF(("supR3HardNtChildWaitFor: Found expected request %d (%s) after %llu ms.\n", + enmExpectedRequest, pszWhat, supR3HardenedWinGetMilliTS() - uMsTsStart)); + return; /* Expected request received. */ + } + + /* + * Process termination? + */ + if ( (ULONG)rcNtWait - (ULONG)STATUS_WAIT_0 < cHandles + || (ULONG)rcNtWait - (ULONG)STATUS_ABANDONED_WAIT_0 < cHandles) + break; + + /* + * Check sanity. + */ + if ( rcNtWait != STATUS_TIMEOUT + && rcNtWait != STATUS_USER_APC + && rcNtWait != STATUS_ALERTED) + supR3HardenedWinKillChild(pThis, "supR3HardNtChildWaitFor", rcNtWait, + "NtWaitForMultipleObjects returned %#x waiting for #%d (%s)\n", + rcNtWait, enmExpectedRequest, pszWhat); + + /* + * Calc elapsed time for the next timeout calculation, checking to see + * if we've timed out already. + */ + cMsElapsed = supR3HardenedWinGetMilliTS() - uMsTsStart; + if ( cMsElapsed > cMsTimeout + && cMsTimeout != RT_INDEFINITE_WAIT + && enmExpectedRequest != kSupR3WinChildReq_End) + { + if (rcNtWait == STATUS_USER_APC || rcNtWait == STATUS_ALERTED) + cMsElapsed = cMsTimeout - 1; /* try again */ + else + { + /* We timed out. */ + supR3HardenedWinKillChild(pThis, "supR3HardNtChildWaitFor", rcNtWait, + "Timed out after %llu ms waiting for child request #%d (%s).\n", + cMsElapsed, enmExpectedRequest, pszWhat); + } + } + } + + /* + * Proxy the termination code of the child, if it exited already. + */ + PROCESS_BASIC_INFORMATION BasicInfo; + NTSTATUS rcNt1 = NtQueryInformationProcess(pThis->hProcess, ProcessBasicInformation, &BasicInfo, sizeof(BasicInfo), NULL); + NTSTATUS rcNt2 = STATUS_PENDING; + NTSTATUS rcNt3 = STATUS_PENDING; + if ( !NT_SUCCESS(rcNt1) + || BasicInfo.ExitStatus == STATUS_PENDING) + { + rcNt2 = NtTerminateProcess(pThis->hProcess, RTEXITCODE_FAILURE); + Timeout.QuadPart = NT_SUCCESS(rcNt2) ? -20000000 /* 2 sec */ : -1280000 /* 128 ms */; + rcNt3 = NtWaitForSingleObject(pThis->hProcess, FALSE /*Alertable*/, NULL /*Timeout*/); + BasicInfo.ExitStatus = RTEXITCODE_FAILURE; + } + + SUP_DPRINTF(("supR3HardNtChildWaitFor[%d]: Quitting: ExitCode=%#x (rcNtWait=%#x, rcNt1=%#x, rcNt2=%#x, rcNt3=%#x, %llu ms, %s);\n", + pThis->iWhich, BasicInfo.ExitStatus, rcNtWait, rcNt1, rcNt2, rcNt3, + supR3HardenedWinGetMilliTS() - uMsTsStart, pszWhat)); + suplibHardenedExit((RTEXITCODE)BasicInfo.ExitStatus); +} + + +/** + * Closes full access child thread and process handles, making a harmless + * duplicate of the process handle first. + * + * The hProcess member of the child process data structure will be change to the + * harmless handle, while the hThread will be set to NULL. + * + * @param pThis The child process data structure. + */ +static void supR3HardNtChildCloseFullAccessHandles(PSUPR3HARDNTCHILD pThis) +{ + /* + * The thread handle. + */ + NTSTATUS rcNt = NtClose(pThis->hThread); + if (!NT_SUCCESS(rcNt)) + supR3HardenedWinKillChild(pThis, "supR3HardenedWinReSpawn", rcNt, "NtClose(hThread) failed: %#x", rcNt); + pThis->hThread = NULL; + + /* + * Duplicate the process handle into a harmless one. + */ + HANDLE hProcWait; + ULONG fRights = SYNCHRONIZE | PROCESS_TERMINATE | PROCESS_VM_READ; + if (g_uNtVerCombined >= SUP_MAKE_NT_VER_SIMPLE(6, 0)) /* Introduced in Vista. */ + fRights |= PROCESS_QUERY_LIMITED_INFORMATION; + else + fRights |= PROCESS_QUERY_INFORMATION; + rcNt = NtDuplicateObject(NtCurrentProcess(), pThis->hProcess, + NtCurrentProcess(), &hProcWait, + fRights, 0 /*HandleAttributes*/, 0); + if (rcNt == STATUS_ACCESS_DENIED) + { + supR3HardenedError(rcNt, false /*fFatal*/, + "supR3HardenedWinDoReSpawn: NtDuplicateObject(,,,,%#x,,) -> %#x, retrying with only %#x...\n", + fRights, rcNt, SYNCHRONIZE); + rcNt = NtDuplicateObject(NtCurrentProcess(), pThis->hProcess, + NtCurrentProcess(), &hProcWait, + SYNCHRONIZE, 0 /*HandleAttributes*/, 0); + } + if (!NT_SUCCESS(rcNt)) + supR3HardenedWinKillChild(pThis, "supR3HardenedWinReSpawn", rcNt, + "NtDuplicateObject failed on child process handle: %#x\n", rcNt); + /* + * Close the process handle and replace it with the harmless one. + */ + rcNt = NtClose(pThis->hProcess); + pThis->hProcess = hProcWait; + if (!NT_SUCCESS(rcNt)) + supR3HardenedWinKillChild(pThis, "supR3HardenedWinReSpawn", VERR_INVALID_NAME, + "NtClose failed on child process handle: %#x\n", rcNt); +} + + +/** + * This restores the child PEB and tweaks a couple of fields before we do the + * child purification and let the process run normally. + * + * @param pThis The child process data structure. + */ +static void supR3HardNtChildSanitizePeb(PSUPR3HARDNTCHILD pThis) +{ + /* + * Make a copy of the pre-execution PEB. + */ + PEB Peb = pThis->Peb; + +#if 0 + /* + * There should not be any activation context, so if there is, we scratch the memory associated with it. + */ + int rc = 0; + if (RT_SUCCESS(rc) && Peb.pShimData && !((uintptr_t)Peb.pShimData & PAGE_OFFSET_MASK)) + rc = supR3HardenedWinScratchChildMemory(hProcess, Peb.pShimData, PAGE_SIZE, "pShimData", pErrInfo); + if (RT_SUCCESS(rc) && Peb.ActivationContextData && !((uintptr_t)Peb.ActivationContextData & PAGE_OFFSET_MASK)) + rc = supR3HardenedWinScratchChildMemory(hProcess, Peb.ActivationContextData, PAGE_SIZE, "ActivationContextData", pErrInfo); + if (RT_SUCCESS(rc) && Peb.ProcessAssemblyStorageMap && !((uintptr_t)Peb.ProcessAssemblyStorageMap & PAGE_OFFSET_MASK)) + rc = supR3HardenedWinScratchChildMemory(hProcess, Peb.ProcessAssemblyStorageMap, PAGE_SIZE, "ProcessAssemblyStorageMap", pErrInfo); + if (RT_SUCCESS(rc) && Peb.SystemDefaultActivationContextData && !((uintptr_t)Peb.SystemDefaultActivationContextData & PAGE_OFFSET_MASK)) + rc = supR3HardenedWinScratchChildMemory(hProcess, Peb.ProcessAssemblyStorageMap, PAGE_SIZE, "SystemDefaultActivationContextData", pErrInfo); + if (RT_SUCCESS(rc) && Peb.SystemAssemblyStorageMap && !((uintptr_t)Peb.SystemAssemblyStorageMap & PAGE_OFFSET_MASK)) + rc = supR3HardenedWinScratchChildMemory(hProcess, Peb.SystemAssemblyStorageMap, PAGE_SIZE, "SystemAssemblyStorageMap", pErrInfo); + if (RT_FAILURE(rc)) + return rc; +#endif + + /* + * Clear compatibility and activation related fields. + */ + Peb.AppCompatFlags.QuadPart = 0; + Peb.AppCompatFlagsUser.QuadPart = 0; + Peb.pShimData = NULL; + Peb.AppCompatInfo = NULL; +#if 0 + Peb.ActivationContextData = NULL; + Peb.ProcessAssemblyStorageMap = NULL; + Peb.SystemDefaultActivationContextData = NULL; + Peb.SystemAssemblyStorageMap = NULL; + /*Peb.Diff0.W6.IsProtectedProcess = 1;*/ +#endif + + /* + * Write back the PEB. + */ + SIZE_T cbActualMem = pThis->cbPeb; + NTSTATUS rcNt = NtWriteVirtualMemory(pThis->hProcess, pThis->BasicInfo.PebBaseAddress, &Peb, pThis->cbPeb, &cbActualMem); + if (!NT_SUCCESS(rcNt)) + supR3HardenedWinKillChild(pThis, "supR3HardNtChildSanitizePeb", rcNt, + "NtWriteVirtualMemory/Peb failed: %#x", rcNt); + +} + + +/** + * Purifies the child process after very early init has been performed. + * + * @param pThis The child process data structure. + */ +static void supR3HardNtChildPurify(PSUPR3HARDNTCHILD pThis) +{ + /* + * We loop until we no longer make any fixes. This is similar to what + * we do (or used to do, really) in the fAvastKludge case of + * supR3HardenedWinInit. We might be up against asynchronous changes, + * which we fudge by waiting a short while before earch purification. This + * is arguably a fragile technique, but it's currently the best we've got. + * Fortunately, most AVs seems to either favor immediate action on initial + * load events or (much better for us) later events like kernel32. + */ + uint64_t uMsTsOuterStart = supR3HardenedWinGetMilliTS(); + uint32_t cMsFudge = g_fSupAdversaries ? 512 : 256; + uint32_t cTotalFixes = 0; + uint32_t cFixes = 0; /* (MSC wrongly thinks this maybe used uninitialized) */ + for (uint32_t iLoop = 0; iLoop < 16; iLoop++) + { + /* + * Delay. + */ + uint32_t cSleeps = 0; + uint64_t uMsTsStart = supR3HardenedWinGetMilliTS(); + do + { + NtYieldExecution(); + LARGE_INTEGER Time; + Time.QuadPart = -8000000 / 100; /* 8ms in 100ns units, relative time. */ + NtDelayExecution(FALSE, &Time); + cSleeps++; + } while ( supR3HardenedWinGetMilliTS() - uMsTsStart <= cMsFudge + || cSleeps < 8); + SUP_DPRINTF(("supR3HardNtChildPurify: Startup delay kludge #1/%u: %u ms, %u sleeps\n", + iLoop, supR3HardenedWinGetMilliTS() - uMsTsStart, cSleeps)); + + /* + * Purify. + */ + cFixes = 0; + int rc = supHardenedWinVerifyProcess(pThis->hProcess, pThis->hThread, SUPHARDNTVPKIND_CHILD_PURIFICATION, + g_fSupAdversaries & ( SUPHARDNT_ADVERSARY_TRENDMICRO_SAKFILE + | SUPHARDNT_ADVERSARY_DIGITAL_GUARDIAN_OLD) + ? SUPHARDNTVP_F_EXEC_ALLOC_REPLACE_WITH_RW : 0, + &cFixes, RTErrInfoInitStatic(&g_ErrInfoStatic)); + if (RT_FAILURE(rc)) + supR3HardenedWinKillChild(pThis, "supR3HardNtChildPurify", rc, + "supHardenedWinVerifyProcess failed with %Rrc: %s", rc, g_ErrInfoStatic.szMsg); + if (cFixes == 0) + { + SUP_DPRINTF(("supR3HardNtChildPurify: Done after %llu ms and %u fixes (loop #%u).\n", + supR3HardenedWinGetMilliTS() - uMsTsOuterStart, cTotalFixes, iLoop)); + return; /* We're probably good. */ + } + cTotalFixes += cFixes; + + if (!g_fSupAdversaries) + g_fSupAdversaries |= SUPHARDNT_ADVERSARY_UNKNOWN; + cMsFudge = 512; + + /* + * Log the KiOpPrefetchPatchCount value if available, hoping it might + * sched some light on spider38's case. + */ + ULONG cPatchCount = 0; + NTSTATUS rcNt = NtQuerySystemInformation(SystemInformation_KiOpPrefetchPatchCount, + &cPatchCount, sizeof(cPatchCount), NULL); + if (NT_SUCCESS(rcNt)) + SUP_DPRINTF(("supR3HardNtChildPurify: cFixes=%u g_fSupAdversaries=%#x cPatchCount=%#u\n", + cFixes, g_fSupAdversaries, cPatchCount)); + else + SUP_DPRINTF(("supR3HardNtChildPurify: cFixes=%u g_fSupAdversaries=%#x\n", cFixes, g_fSupAdversaries)); + } + + /* + * We've given up fixing the child process. Probably fighting someone + * that monitors their patches or/and our activities. + */ + supR3HardenedWinKillChild(pThis, "supR3HardNtChildPurify", VERR_TRY_AGAIN, + "Unable to purify child process! After 16 tries over %llu ms, we still %u fix(es) in the last pass.", + supR3HardenedWinGetMilliTS() - uMsTsOuterStart, cFixes); +} + + +/** + * Sets up the early process init. + * + * @param pThis The child process data structure. + */ +static void supR3HardNtChildSetUpChildInit(PSUPR3HARDNTCHILD pThis) +{ + uintptr_t const uChildExeAddr = (uintptr_t)pThis->Peb.ImageBaseAddress; + + /* + * Plant the process parameters. This ASSUMES the handle inheritance is + * performed when creating the child process. + */ + RT_ZERO(pThis->ProcParams); + pThis->ProcParams.hEvtChild = pThis->hEvtChild; + pThis->ProcParams.hEvtParent = pThis->hEvtParent; + pThis->ProcParams.uNtDllAddr = pThis->uNtDllAddr; + pThis->ProcParams.enmRequest = kSupR3WinChildReq_Error; + pThis->ProcParams.rc = VINF_SUCCESS; + + uintptr_t uChildAddr = uChildExeAddr + ((uintptr_t)&g_ProcParams - (uintptr_t)NtCurrentPeb()->ImageBaseAddress); + SIZE_T cbIgnored; + NTSTATUS rcNt = NtWriteVirtualMemory(pThis->hProcess, (PVOID)uChildAddr, &pThis->ProcParams, + sizeof(pThis->ProcParams), &cbIgnored); + if (!NT_SUCCESS(rcNt)) + supR3HardenedWinKillChild(pThis, "supR3HardenedWinSetupChildInit", rcNt, + "NtWriteVirtualMemory(,%p,) failed writing child process parameters: %#x\n", uChildAddr, rcNt); + + /* + * Locate the LdrInitializeThunk address in the child as well as pristine + * code bits for it. + */ + PSUPHNTLDRCACHEENTRY pLdrEntry; + int rc = supHardNtLdrCacheOpen("ntdll.dll", &pLdrEntry, NULL /*pErrInfo*/); + if (RT_FAILURE(rc)) + supR3HardenedWinKillChild(pThis, "supR3HardenedWinSetupChildInit", rc, + "supHardNtLdrCacheOpen failed on NTDLL: %Rrc\n", rc); + + uint8_t *pbChildNtDllBits; + rc = supHardNtLdrCacheEntryGetBits(pLdrEntry, &pbChildNtDllBits, pThis->uNtDllAddr, NULL, NULL, NULL /*pErrInfo*/); + if (RT_FAILURE(rc)) + supR3HardenedWinKillChild(pThis, "supR3HardenedWinSetupChildInit", rc, + "supHardNtLdrCacheEntryGetBits failed on NTDLL: %Rrc\n", rc); + + RTLDRADDR uLdrInitThunk; + rc = RTLdrGetSymbolEx(pLdrEntry->hLdrMod, pbChildNtDllBits, pThis->uNtDllAddr, UINT32_MAX, + "LdrInitializeThunk", &uLdrInitThunk); + if (RT_FAILURE(rc)) + supR3HardenedWinKillChild(pThis, "supR3HardenedWinSetupChildInit", rc, + "Error locating LdrInitializeThunk in NTDLL: %Rrc", rc); + PVOID pvLdrInitThunk = (PVOID)(uintptr_t)uLdrInitThunk; + SUP_DPRINTF(("supR3HardenedWinSetupChildInit: uLdrInitThunk=%p\n", (uintptr_t)uLdrInitThunk)); + + /* + * Calculate the address of our code in the child process. + */ + uintptr_t uEarlyProcInitEP = uChildExeAddr + ( (uintptr_t)&supR3HardenedEarlyProcessInitThunk + - (uintptr_t)NtCurrentPeb()->ImageBaseAddress); + + /* + * Compose the LdrInitializeThunk replacement bytes. + * Note! The amount of code we replace here must be less or equal to what + * the process verification code ignores. + */ + uint8_t abNew[16]; + memcpy(abNew, pbChildNtDllBits + ((uintptr_t)uLdrInitThunk - pThis->uNtDllAddr), sizeof(abNew)); +#ifdef RT_ARCH_AMD64 + abNew[0] = 0xff; + abNew[1] = 0x25; + *(uint32_t *)&abNew[2] = 0; + *(uint64_t *)&abNew[6] = uEarlyProcInitEP; +#elif defined(RT_ARCH_X86) + abNew[0] = 0xe9; + *(uint32_t *)&abNew[1] = uEarlyProcInitEP - ((uint32_t)uLdrInitThunk + 5); +#else +# error "Unsupported arch." +#endif + + /* + * Install the LdrInitializeThunk replacement code in the child process. + */ + PVOID pvProt = pvLdrInitThunk; + SIZE_T cbProt = sizeof(abNew); + ULONG fOldProt; + rcNt = NtProtectVirtualMemory(pThis->hProcess, &pvProt, &cbProt, PAGE_EXECUTE_READWRITE, &fOldProt); + if (!NT_SUCCESS(rcNt)) + supR3HardenedWinKillChild(pThis, "supR3HardenedWinSetupChildInit", rcNt, + "NtProtectVirtualMemory/LdrInitializeThunk failed: %#x", rcNt); + + rcNt = NtWriteVirtualMemory(pThis->hProcess, pvLdrInitThunk, abNew, sizeof(abNew), &cbIgnored); + if (!NT_SUCCESS(rcNt)) + supR3HardenedWinKillChild(pThis, "supR3HardenedWinSetupChildInit", rcNt, + "NtWriteVirtualMemory/LdrInitializeThunk failed: %#x", rcNt); + + pvProt = pvLdrInitThunk; + cbProt = sizeof(abNew); + rcNt = NtProtectVirtualMemory(pThis->hProcess, &pvProt, &cbProt, fOldProt, &fOldProt); + if (!NT_SUCCESS(rcNt)) + supR3HardenedWinKillChild(pThis, "supR3HardenedWinSetupChildInit", rcNt, + "NtProtectVirtualMemory/LdrInitializeThunk[restore] failed: %#x", rcNt); + + /* + * Check the sanity of the thread context. + */ + CONTEXT Ctx; + RT_ZERO(Ctx); + Ctx.ContextFlags = CONTEXT_FULL | CONTEXT_DEBUG_REGISTERS; + rcNt = NtGetContextThread(pThis->hThread, &Ctx); + if (NT_SUCCESS(rcNt)) + { +#ifdef RT_ARCH_AMD64 + DWORD64 *pPC = &Ctx.Rip; +#elif defined(RT_ARCH_X86) + DWORD *pPC = &Ctx.Eip; +#else +# error "Unsupported arch." +#endif + supR3HardNtDprintCtx(&Ctx, "supR3HardenedWinSetupChildInit: Initial context:"); + + /* Entrypoint for the executable: */ + uintptr_t const uChildMain = uChildExeAddr + ( (uintptr_t)&suplibHardenedWindowsMain + - (uintptr_t)NtCurrentPeb()->ImageBaseAddress); + + /* NtDll size and the more recent default thread start entrypoint (Vista+?): */ + RTLDRADDR uSystemThreadStart; + rc = RTLdrGetSymbolEx(pLdrEntry->hLdrMod, pbChildNtDllBits, pThis->uNtDllAddr, UINT32_MAX, + "RtlUserThreadStart", &uSystemThreadStart); + if (RT_FAILURE(rc)) + uSystemThreadStart = 0; + + /* Kernel32 for thread start of older windows version, only XP64/W2K3-64 has an actual + export for it. Unfortunately, it is not yet loaded into the child, so we have to + assume same location as in the parent (safe): */ + PSUPHNTLDRCACHEENTRY pLdrEntryKernel32; + rc = supHardNtLdrCacheOpen("kernel32.dll", &pLdrEntryKernel32, NULL /*pErrInfo*/); + if (RT_FAILURE(rc)) + supR3HardenedWinKillChild(pThis, "supR3HardenedWinSetupChildInit", rc, + "supHardNtLdrCacheOpen failed on KERNEL32: %Rrc\n", rc); + size_t const cbKernel32 = RTLdrSize(pLdrEntryKernel32->hLdrMod); + +#ifdef RT_ARCH_AMD64 + if (!uSystemThreadStart) + { + rc = RTLdrGetSymbolEx(pLdrEntry->hLdrMod, pbChildNtDllBits, pLdrEntryKernel32->uImageBase, UINT32_MAX, + "BaseProcessStart", &uSystemThreadStart); + if (RT_FAILURE(rc)) + uSystemThreadStart = 0; + } +#endif + + bool fUpdateContext = false; + + /* Check if the RIP looks half sane, try correct it if it isn't. + It should point to RtlUserThreadStart (Vista and later it seem), though only + tested on win10. The first parameter is the executable entrypoint, the 2nd + is probably the PEB. Before Vista it should point to Kernel32!BaseProcessStart, + though the symbol is only exported in 5.2/AMD64. */ + if ( ( uSystemThreadStart + ? *pPC == uSystemThreadStart + : *pPC - ( pLdrEntryKernel32->uImageBase != ~(uintptr_t)0 ? pLdrEntryKernel32->uImageBase + : (uintptr_t)GetModuleHandleW(L"kernel32.dll")) <= cbKernel32) + || *pPC == uChildMain) + { } + else + { + SUP_DPRINTF(("Warning! Bogus RIP: %p (uSystemThreadStart=%p; kernel32 %p LB %p; uChildMain=%p)\n", + *pPC, uSystemThreadStart, pLdrEntryKernel32->uImageBase, cbKernel32, uChildMain)); + if (uSystemThreadStart) + { + SUP_DPRINTF(("Correcting RIP from to %p hoping that it might work...\n", (uintptr_t)uSystemThreadStart)); + *pPC = uSystemThreadStart; + fUpdateContext = true; + } + } +#ifdef RT_ARCH_AMD64 + if (g_uNtVerCombined >= SUP_MAKE_NT_VER_SIMPLE(10, 0)) /* W2K3: CS=33 SS=DS=ES=GS=2b FS=53 */ + { + if (Ctx.SegDs != 0) + SUP_DPRINTF(("Warning! Bogus DS: %04x, expected zero\n", Ctx.SegDs)); + if (Ctx.SegEs != 0) + SUP_DPRINTF(("Warning! Bogus ES: %04x, expected zero\n", Ctx.SegEs)); + if (Ctx.SegFs != 0) + SUP_DPRINTF(("Warning! Bogus FS: %04x, expected zero\n", Ctx.SegFs)); + if (Ctx.SegGs != 0) + SUP_DPRINTF(("Warning! Bogus GS: %04x, expected zero\n", Ctx.SegGs)); + } + if (Ctx.Rcx != uChildMain) + SUP_DPRINTF(("Warning! Bogus RCX: %016RX64, expected %016RX64\n", Ctx.Rcx, uChildMain)); + if (Ctx.Rdx & PAGE_OFFSET_MASK) + SUP_DPRINTF(("Warning! Bogus RDX: %016RX64, expected page aligned\n", Ctx.Rdx)); /* PEB */ + if ((Ctx.Rsp & 15) != 8) + SUP_DPRINTF(("Warning! Misaligned RSP: %016RX64\n", Ctx.Rsp)); +#endif + if (Ctx.SegCs != ASMGetCS()) + SUP_DPRINTF(("Warning! Bogus CS: %04x, expected %04x\n", Ctx.SegCs, ASMGetCS())); + if (Ctx.SegSs != ASMGetSS()) + SUP_DPRINTF(("Warning! Bogus SS: %04x, expected %04x\n", Ctx.SegSs, ASMGetSS())); + if (Ctx.Dr0 != 0) + SUP_DPRINTF(("Warning! Bogus DR0: %016RX64, expected zero\n", Ctx.Dr0)); + if (Ctx.Dr1 != 0) + SUP_DPRINTF(("Warning! Bogus DR1: %016RX64, expected zero\n", Ctx.Dr1)); + if (Ctx.Dr2 != 0) + SUP_DPRINTF(("Warning! Bogus DR2: %016RX64, expected zero\n", Ctx.Dr2)); + if (Ctx.Dr3 != 0) + SUP_DPRINTF(("Warning! Bogus DR3: %016RX64, expected zero\n", Ctx.Dr3)); + if (Ctx.Dr6 != 0) + SUP_DPRINTF(("Warning! Bogus DR6: %016RX64, expected zero\n", Ctx.Dr6)); + if (Ctx.Dr7 != 0) + { + SUP_DPRINTF(("Warning! Bogus DR7: %016RX64, expected zero\n", Ctx.Dr7)); + Ctx.Dr7 = 0; + fUpdateContext = true; + } + + if (fUpdateContext) + { + rcNt = NtSetContextThread(pThis->hThread, &Ctx); + if (!NT_SUCCESS(rcNt)) + SUP_DPRINTF(("Error! NtSetContextThread failed: %#x\n", rcNt)); + } + } + + /* Caller starts child execution. */ + SUP_DPRINTF(("supR3HardenedWinSetupChildInit: Start child.\n")); +} + + + +/** + * This messes with the child PEB before we trigger the initial image events. + * + * @param pThis The child process data structure. + */ +static void supR3HardNtChildScrewUpPebForInitialImageEvents(PSUPR3HARDNTCHILD pThis) +{ + /* + * Not sure if any of the cracker software uses the PEB at this point, but + * just in case they do make some of the PEB fields a little less useful. + */ + PEB Peb = pThis->Peb; + + /* Make ImageBaseAddress useless. */ + Peb.ImageBaseAddress = (PVOID)((uintptr_t)Peb.ImageBaseAddress ^ UINT32_C(0x5f139000)); +#ifdef RT_ARCH_AMD64 + Peb.ImageBaseAddress = (PVOID)((uintptr_t)Peb.ImageBaseAddress | UINT64_C(0x0313000000000000)); +#endif + + /* + * Write the PEB. + */ + SIZE_T cbActualMem = pThis->cbPeb; + NTSTATUS rcNt = NtWriteVirtualMemory(pThis->hProcess, pThis->BasicInfo.PebBaseAddress, &Peb, pThis->cbPeb, &cbActualMem); + if (!NT_SUCCESS(rcNt)) + supR3HardenedWinKillChild(pThis, "supR3HardNtChildScrewUpPebForInitialImageEvents", rcNt, + "NtWriteVirtualMemory/Peb failed: %#x", rcNt); +} + + +/** + * Check if the zero terminated NT unicode string is the path to the given + * system32 DLL. + * + * @returns true if it is, false if not. + * @param pUniStr The zero terminated NT unicode string path. + * @param pszName The name of the system32 DLL. + */ +static bool supR3HardNtIsNamedSystem32Dll(PUNICODE_STRING pUniStr, const char *pszName) +{ + if (pUniStr->Length > g_System32NtPath.UniStr.Length) + { + if (memcmp(pUniStr->Buffer, g_System32NtPath.UniStr.Buffer, g_System32NtPath.UniStr.Length) == 0) + { + if (pUniStr->Buffer[g_System32NtPath.UniStr.Length / sizeof(WCHAR)] == '\\') + { + if (RTUtf16ICmpAscii(&pUniStr->Buffer[g_System32NtPath.UniStr.Length / sizeof(WCHAR) + 1], pszName) == 0) + return true; + } + } + } + + return false; +} + + +/** + * Worker for supR3HardNtChildGatherData that locates NTDLL in the child + * process. + * + * @param pThis The child process data structure. + */ +static void supR3HardNtChildFindNtdll(PSUPR3HARDNTCHILD pThis) +{ + /* + * Find NTDLL in this process first and take that as a starting point. + */ + pThis->uNtDllParentAddr = (uintptr_t)GetModuleHandleW(L"ntdll.dll"); + SUPR3HARDENED_ASSERT(pThis->uNtDllParentAddr != 0 && !(pThis->uNtDllParentAddr & PAGE_OFFSET_MASK)); + pThis->uNtDllAddr = pThis->uNtDllParentAddr; + + /* + * Scan the virtual memory of the child. + */ + uintptr_t cbAdvance = 0; + uintptr_t uPtrWhere = 0; + for (uint32_t i = 0; i < 1024; i++) + { + /* Query information. */ + SIZE_T cbActual = 0; + MEMORY_BASIC_INFORMATION MemInfo = { 0, 0, 0, 0, 0, 0, 0 }; + NTSTATUS rcNt = NtQueryVirtualMemory(pThis->hProcess, + (void const *)uPtrWhere, + MemoryBasicInformation, + &MemInfo, + sizeof(MemInfo), + &cbActual); + if (!NT_SUCCESS(rcNt)) + break; + + if ( MemInfo.Type == SEC_IMAGE + || MemInfo.Type == SEC_PROTECTED_IMAGE + || MemInfo.Type == (SEC_IMAGE | SEC_PROTECTED_IMAGE)) + { + if (MemInfo.BaseAddress == MemInfo.AllocationBase) + { + /* Get the image name. */ + union + { + UNICODE_STRING UniStr; + uint8_t abPadding[4096]; + } uBuf; + rcNt = NtQueryVirtualMemory(pThis->hProcess, + MemInfo.BaseAddress, + MemorySectionName, + &uBuf, + sizeof(uBuf) - sizeof(WCHAR), + &cbActual); + if (NT_SUCCESS(rcNt)) + { + uBuf.UniStr.Buffer[uBuf.UniStr.Length / sizeof(WCHAR)] = '\0'; + if (supR3HardNtIsNamedSystem32Dll(&uBuf.UniStr, "ntdll.dll")) + { + pThis->uNtDllAddr = (uintptr_t)MemInfo.AllocationBase; + SUP_DPRINTF(("supR3HardNtPuChFindNtdll: uNtDllParentAddr=%p uNtDllChildAddr=%p\n", + pThis->uNtDllParentAddr, pThis->uNtDllAddr)); + return; + } + } + } + } + + /* + * Advance. + */ + cbAdvance = MemInfo.RegionSize; + if (uPtrWhere + cbAdvance <= uPtrWhere) + break; + uPtrWhere += MemInfo.RegionSize; + } + + supR3HardenedWinKillChild(pThis, "supR3HardNtChildFindNtdll", VERR_MODULE_NOT_FOUND, "ntdll.dll not found in child process."); +} + + +/** + * Gather child data. + * + * @param pThis The child process data structure. + */ +static void supR3HardNtChildGatherData(PSUPR3HARDNTCHILD pThis) +{ + /* + * Basic info. + */ + ULONG cbActual = 0; + NTSTATUS rcNt = NtQueryInformationProcess(pThis->hProcess, ProcessBasicInformation, + &pThis->BasicInfo, sizeof(pThis->BasicInfo), &cbActual); + if (!NT_SUCCESS(rcNt)) + supR3HardenedWinKillChild(pThis, "supR3HardNtChildGatherData", rcNt, + "NtQueryInformationProcess/ProcessBasicInformation failed: %#x", rcNt); + + /* + * If this is the middle (stub) process, we wish to wait for both child + * and parent. So open the parent process. Not fatal if we cannnot. + */ + if (pThis->iWhich > 1) + { + PROCESS_BASIC_INFORMATION SelfInfo; + rcNt = NtQueryInformationProcess(NtCurrentProcess(), ProcessBasicInformation, &SelfInfo, sizeof(SelfInfo), &cbActual); + if (NT_SUCCESS(rcNt)) + { + OBJECT_ATTRIBUTES ObjAttr; + InitializeObjectAttributes(&ObjAttr, NULL, 0, NULL /*hRootDir*/, NULL /*pSecDesc*/); + + CLIENT_ID ClientId; + ClientId.UniqueProcess = (HANDLE)SelfInfo.InheritedFromUniqueProcessId; + ClientId.UniqueThread = NULL; + + rcNt = NtOpenProcess(&pThis->hParent, SYNCHRONIZE | PROCESS_QUERY_INFORMATION, &ObjAttr, &ClientId); +#ifdef DEBUG + SUPR3HARDENED_ASSERT_NT_SUCCESS(rcNt); +#endif + if (!NT_SUCCESS(rcNt)) + { + pThis->hParent = NULL; + SUP_DPRINTF(("supR3HardNtChildGatherData: Failed to open parent process (%#p): %#x\n", ClientId.UniqueProcess, rcNt)); + } + } + + } + + /* + * Process environment block. + */ + if (g_uNtVerCombined < SUP_NT_VER_W2K3) + pThis->cbPeb = PEB_SIZE_W51; + else if (g_uNtVerCombined < SUP_NT_VER_VISTA) + pThis->cbPeb = PEB_SIZE_W52; + else if (g_uNtVerCombined < SUP_NT_VER_W70) + pThis->cbPeb = PEB_SIZE_W6; + else if (g_uNtVerCombined < SUP_NT_VER_W80) + pThis->cbPeb = PEB_SIZE_W7; + else if (g_uNtVerCombined < SUP_NT_VER_W81) + pThis->cbPeb = PEB_SIZE_W80; + else + pThis->cbPeb = PEB_SIZE_W81; + + SUP_DPRINTF(("supR3HardNtChildGatherData: PebBaseAddress=%p cbPeb=%#x\n", + pThis->BasicInfo.PebBaseAddress, pThis->cbPeb)); + + SIZE_T cbActualMem; + RT_ZERO(pThis->Peb); + rcNt = NtReadVirtualMemory(pThis->hProcess, pThis->BasicInfo.PebBaseAddress, &pThis->Peb, sizeof(pThis->Peb), &cbActualMem); + if (!NT_SUCCESS(rcNt)) + supR3HardenedWinKillChild(pThis, "supR3HardNtChildGatherData", rcNt, + "NtReadVirtualMemory/Peb failed: %#x", rcNt); + + /* + * Locate NtDll. + */ + supR3HardNtChildFindNtdll(pThis); +} + + +/** + * Does the actually respawning. + * + * @returns Never, will call exit or raise fatal error. + * @param iWhich Which respawn we're to check for, 1 being the + * first one, and 2 the second and final. + */ +static DECL_NO_RETURN(void) supR3HardenedWinDoReSpawn(int iWhich) +{ + NTSTATUS rcNt; + PPEB pPeb = NtCurrentPeb(); + PRTL_USER_PROCESS_PARAMETERS pParentProcParams = pPeb->ProcessParameters; + + SUPR3HARDENED_ASSERT(g_cSuplibHardenedWindowsMainCalls == 1); + + /* + * Init the child process data structure, creating the child communication + * event sempahores. + */ + SUPR3HARDNTCHILD This; + RT_ZERO(This); + This.iWhich = iWhich; + + OBJECT_ATTRIBUTES ObjAttrs; + This.hEvtChild = NULL; + InitializeObjectAttributes(&ObjAttrs, NULL /*pName*/, OBJ_INHERIT, NULL /*hRootDir*/, NULL /*pSecDesc*/); + SUPR3HARDENED_ASSERT_NT_SUCCESS(NtCreateEvent(&This.hEvtChild, EVENT_ALL_ACCESS, &ObjAttrs, SynchronizationEvent, FALSE)); + + This.hEvtParent = NULL; + InitializeObjectAttributes(&ObjAttrs, NULL /*pName*/, OBJ_INHERIT, NULL /*hRootDir*/, NULL /*pSecDesc*/); + SUPR3HARDENED_ASSERT_NT_SUCCESS(NtCreateEvent(&This.hEvtParent, EVENT_ALL_ACCESS, &ObjAttrs, SynchronizationEvent, FALSE)); + + /* + * Set up security descriptors. + */ + SECURITY_ATTRIBUTES ProcessSecAttrs; + MYSECURITYCLEANUP ProcessSecAttrsCleanup; + supR3HardNtChildInitSecAttrs(&ProcessSecAttrs, &ProcessSecAttrsCleanup, true /*fProcess*/); + + SECURITY_ATTRIBUTES ThreadSecAttrs; + MYSECURITYCLEANUP ThreadSecAttrsCleanup; + supR3HardNtChildInitSecAttrs(&ThreadSecAttrs, &ThreadSecAttrsCleanup, false /*fProcess*/); + +#if 1 + /* + * Configure the startup info and creation flags. + */ + DWORD dwCreationFlags = CREATE_SUSPENDED; + + STARTUPINFOEXW SiEx; + suplibHardenedMemSet(&SiEx, 0, sizeof(SiEx)); + if (1) + SiEx.StartupInfo.cb = sizeof(SiEx.StartupInfo); + else + { + SiEx.StartupInfo.cb = sizeof(SiEx); + dwCreationFlags |= EXTENDED_STARTUPINFO_PRESENT; + /** @todo experiment with protected process stuff later on. */ + } + + SiEx.StartupInfo.dwFlags |= pParentProcParams->WindowFlags & STARTF_USESHOWWINDOW; + SiEx.StartupInfo.wShowWindow = (WORD)pParentProcParams->ShowWindowFlags; + + SiEx.StartupInfo.dwFlags |= STARTF_USESTDHANDLES; + SiEx.StartupInfo.hStdInput = pParentProcParams->StandardInput; + SiEx.StartupInfo.hStdOutput = pParentProcParams->StandardOutput; + SiEx.StartupInfo.hStdError = pParentProcParams->StandardError; + + /* + * Construct the command line and launch the process. + */ + PRTUTF16 pwszCmdLine = supR3HardNtChildConstructCmdLine(NULL, iWhich); + + supR3HardenedWinEnableThreadCreation(); + PROCESS_INFORMATION ProcessInfoW32 = { NULL, NULL, 0, 0 }; + if (!CreateProcessW(g_wszSupLibHardenedExePath, + pwszCmdLine, + &ProcessSecAttrs, + &ThreadSecAttrs, + TRUE /*fInheritHandles*/, + dwCreationFlags, + NULL /*pwszzEnvironment*/, + NULL /*pwszCurDir*/, + &SiEx.StartupInfo, + &ProcessInfoW32)) + supR3HardenedFatalMsg("supR3HardenedWinReSpawn", kSupInitOp_Misc, VERR_INVALID_NAME, + "Error relaunching VirtualBox VM process: %u\n" + "Command line: '%ls'", + RtlGetLastWin32Error(), pwszCmdLine); + supR3HardenedWinDisableThreadCreation(); + + SUP_DPRINTF(("supR3HardenedWinDoReSpawn(%d): New child %x.%x [kernel32].\n", + iWhich, ProcessInfoW32.dwProcessId, ProcessInfoW32.dwThreadId)); + This.hProcess = ProcessInfoW32.hProcess; + This.hThread = ProcessInfoW32.hThread; + +#else + + /* + * Construct the process parameters. + */ + UNICODE_STRING W32ImageName; + W32ImageName.Buffer = g_wszSupLibHardenedExePath; /* Yes the windows name for the process parameters. */ + W32ImageName.Length = (USHORT)RTUtf16Len(g_wszSupLibHardenedExePath) * sizeof(WCHAR); + W32ImageName.MaximumLength = W32ImageName.Length + sizeof(WCHAR); + + UNICODE_STRING CmdLine; + supR3HardNtChildConstructCmdLine(&CmdLine, iWhich); + + PRTL_USER_PROCESS_PARAMETERS pProcParams = NULL; + SUPR3HARDENED_ASSERT_NT_SUCCESS(RtlCreateProcessParameters(&pProcParams, + &W32ImageName, + NULL /* DllPath - inherit from this process */, + NULL /* CurrentDirectory - inherit from this process */, + &CmdLine, + NULL /* Environment - inherit from this process */, + NULL /* WindowsTitle - none */, + NULL /* DesktopTitle - none. */, + NULL /* ShellInfo - none. */, + NULL /* RuntimeInfo - none (byte array for MSVCRT file info) */) + ); + + /** @todo this doesn't work. :-( */ + pProcParams->ConsoleHandle = pParentProcParams->ConsoleHandle; + pProcParams->ConsoleFlags = pParentProcParams->ConsoleFlags; + pProcParams->StandardInput = pParentProcParams->StandardInput; + pProcParams->StandardOutput = pParentProcParams->StandardOutput; + pProcParams->StandardError = pParentProcParams->StandardError; + + RTL_USER_PROCESS_INFORMATION ProcessInfoNt = { sizeof(ProcessInfoNt) }; + rcNt = RtlCreateUserProcess(&g_SupLibHardenedExeNtPath.UniStr, + OBJ_INHERIT | OBJ_CASE_INSENSITIVE /*Attributes*/, + pProcParams, + NULL, //&ProcessSecAttrs, + NULL, //&ThreadSecAttrs, + NtCurrentProcess() /* ParentProcess */, + FALSE /*fInheritHandles*/, + NULL /* DebugPort */, + NULL /* ExceptionPort */, + &ProcessInfoNt); + if (!NT_SUCCESS(rcNt)) + supR3HardenedFatalMsg("supR3HardenedWinReSpawn", kSupInitOp_Misc, VERR_INVALID_NAME, + "Error relaunching VirtualBox VM process: %#x\n" + "Command line: '%ls'", + rcNt, CmdLine.Buffer); + + SUP_DPRINTF(("supR3HardenedWinDoReSpawn(%d): New child %x.%x [ntdll].\n", + iWhich, ProcessInfo.ClientId.UniqueProcess, ProcessInfo.ClientId.UniqueThread)); + RtlDestroyProcessParameters(pProcParams); + + This.hProcess = ProcessInfoNt.ProcessHandle; + This.hThread = ProcessInfoNt.ThreadHandle; +#endif + +#ifndef VBOX_WITHOUT_DEBUGGER_CHECKS + /* + * Apply anti debugger notification trick to the thread. (Also done in + * supR3HardenedWinInit.) This may fail with STATUS_ACCESS_DENIED and + * maybe other errors. (Unfortunately, recent (SEP 12.1) of symantec's + * sysplant.sys driver will cause process deadlocks and a shutdown/reboot + * denial of service problem if we hide the initial thread, so we postpone + * this action if we've detected SEP.) + */ + if (!(g_fSupAdversaries & (SUPHARDNT_ADVERSARY_SYMANTEC_SYSPLANT | SUPHARDNT_ADVERSARY_SYMANTEC_N360))) + { + rcNt = NtSetInformationThread(This.hThread, ThreadHideFromDebugger, NULL, 0); + if (!NT_SUCCESS(rcNt)) + SUP_DPRINTF(("supR3HardenedWinReSpawn: NtSetInformationThread/ThreadHideFromDebugger failed: %#x (harmless)\n", rcNt)); + } +#endif + + /* + * Perform very early child initialization. + */ + supR3HardNtChildGatherData(&This); + supR3HardNtChildScrewUpPebForInitialImageEvents(&This); + supR3HardNtChildSetUpChildInit(&This); + + ULONG cSuspendCount = 0; + rcNt = NtResumeThread(This.hThread, &cSuspendCount); + if (!NT_SUCCESS(rcNt)) + supR3HardenedWinKillChild(&This, "supR3HardenedWinDoReSpawn", rcNt, "NtResumeThread failed: %#x", rcNt); + + /* + * Santizie the pre-NTDLL child when it's ready. + * + * AV software and other things injecting themselves into the embryonic + * and budding process to intercept API calls and what not. Unfortunately + * this is also the behavior of viruses, malware and other unfriendly + * software, so we won't stand for it. AV software can scan our image + * as they are loaded via kernel hooks, that's sufficient. No need for + * patching half of NTDLL or messing with the import table of the + * process executable. + */ + supR3HardNtChildWaitFor(&This, kSupR3WinChildReq_PurifyChildAndCloseHandles, 2000 /*ms*/, "PurifyChildAndCloseHandles"); + supR3HardNtChildPurify(&This); + supR3HardNtChildSanitizePeb(&This); + + /* + * Close the unrestricted access handles. Since we need to wait on the + * child process, we'll reopen the process with limited access before doing + * away with the process handle returned by CreateProcess. + */ + supR3HardNtChildCloseFullAccessHandles(&This); + + /* + * Signal the child that we've closed the unrestricted handles and it can + * safely try open the driver. + */ + rcNt = NtSetEvent(This.hEvtChild, NULL); + if (!NT_SUCCESS(rcNt)) + supR3HardenedWinKillChild(&This, "supR3HardenedWinReSpawn", VERR_INVALID_NAME, + "NtSetEvent failed on child process handle: %#x\n", rcNt); + + /* + * Ditch the loader cache so we don't sit on too much memory while waiting. + */ + supR3HardenedWinFlushLoaderCache(); + supR3HardenedWinCompactHeaps(); + + /* + * Enable thread creation at this point so Ctrl-C and Ctrl-Break can be processed. + */ + supR3HardenedWinEnableThreadCreation(); + + /* + * Wait for the child to get to suplibHardenedWindowsMain so we can close the handles. + */ + supR3HardNtChildWaitFor(&This, kSupR3WinChildReq_CloseEvents, 60000 /*ms*/, "CloseEvents"); + + NtClose(This.hEvtChild); + NtClose(This.hEvtParent); + This.hEvtChild = NULL; + This.hEvtParent = NULL; + + /* + * Wait for the process to terminate. + */ + supR3HardNtChildWaitFor(&This, kSupR3WinChildReq_End, RT_INDEFINITE_WAIT, "the end"); + supR3HardenedFatal("supR3HardenedWinDoReSpawn: supR3HardNtChildWaitFor unexpectedly returned!\n"); + /* not reached*/ +} + + +/** + * Logs the content of the given object directory. + * + * @returns true if it exists, false if not. + * @param pszDir The path of the directory to log (ASCII). + */ +static void supR3HardenedWinLogObjDir(const char *pszDir) +{ + /* + * Open the driver object directory. + */ + RTUTF16 wszDir[128]; + int rc = RTUtf16CopyAscii(wszDir, RT_ELEMENTS(wszDir), pszDir); + if (RT_FAILURE(rc)) + { + SUP_DPRINTF(("supR3HardenedWinLogObjDir: RTUtf16CopyAscii -> %Rrc on '%s'\n", rc, pszDir)); + return; + } + + UNICODE_STRING NtDirName; + NtDirName.Buffer = (WCHAR *)wszDir; + NtDirName.Length = (USHORT)(RTUtf16Len(wszDir) * sizeof(WCHAR)); + NtDirName.MaximumLength = NtDirName.Length + sizeof(WCHAR); + + OBJECT_ATTRIBUTES ObjAttr; + InitializeObjectAttributes(&ObjAttr, &NtDirName, OBJ_CASE_INSENSITIVE, NULL /*hRootDir*/, NULL /*pSecDesc*/); + + HANDLE hDir; + NTSTATUS rcNt = NtOpenDirectoryObject(&hDir, DIRECTORY_QUERY | FILE_LIST_DIRECTORY, &ObjAttr); + SUP_DPRINTF(("supR3HardenedWinLogObjDir: %ls => %#x\n", wszDir, rcNt)); + if (!NT_SUCCESS(rcNt)) + return; + + /* + * Enumerate it, looking for the driver. + */ + ULONG uObjDirCtx = 0; + for (;;) + { + uint32_t abBuffer[_64K + _1K]; + ULONG cbActual; + rcNt = NtQueryDirectoryObject(hDir, + abBuffer, + sizeof(abBuffer) - 4, /* minus four for string terminator space. */ + FALSE /*ReturnSingleEntry */, + FALSE /*RestartScan*/, + &uObjDirCtx, + &cbActual); + if (!NT_SUCCESS(rcNt) || cbActual < sizeof(OBJECT_DIRECTORY_INFORMATION)) + { + SUP_DPRINTF(("supR3HardenedWinLogObjDir: NtQueryDirectoryObject => rcNt=%#x cbActual=%#x\n", rcNt, cbActual)); + break; + } + + POBJECT_DIRECTORY_INFORMATION pObjDir = (POBJECT_DIRECTORY_INFORMATION)abBuffer; + while (pObjDir->Name.Length != 0) + { + SUP_DPRINTF((" %.*ls %.*ls\n", + pObjDir->TypeName.Length / sizeof(WCHAR), pObjDir->TypeName.Buffer, + pObjDir->Name.Length / sizeof(WCHAR), pObjDir->Name.Buffer)); + + /* Next directory entry. */ + pObjDir++; + } + } + + /* + * Clean up and return. + */ + NtClose(hDir); +} + + +/** + * Tries to open VBoxDrvErrorInfo and read extra error info from it. + * + * @returns pszErrorInfo. + * @param pszErrorInfo The destination buffer. Will always be + * terminated. + * @param cbErrorInfo The size of the destination buffer. + * @param pszPrefix What to prefix the error info with, if we got + * anything. + */ +DECLHIDDEN(char *) supR3HardenedWinReadErrorInfoDevice(char *pszErrorInfo, size_t cbErrorInfo, const char *pszPrefix) +{ + RT_BZERO(pszErrorInfo, cbErrorInfo); + + /* + * Try open the device. + */ + HANDLE hFile = RTNT_INVALID_HANDLE_VALUE; + IO_STATUS_BLOCK Ios = RTNT_IO_STATUS_BLOCK_INITIALIZER; + UNICODE_STRING NtName = RTNT_CONSTANT_UNISTR(SUPDRV_NT_DEVICE_NAME_ERROR_INFO); + OBJECT_ATTRIBUTES ObjAttr; + InitializeObjectAttributes(&ObjAttr, &NtName, OBJ_CASE_INSENSITIVE, NULL /*hRootDir*/, NULL /*pSecDesc*/); + NTSTATUS rcNt = NtCreateFile(&hFile, + GENERIC_READ, /* No SYNCHRONIZE. */ + &ObjAttr, + &Ios, + NULL /* Allocation Size*/, + FILE_ATTRIBUTE_NORMAL, + FILE_SHARE_READ | FILE_SHARE_WRITE, + FILE_OPEN, + FILE_NON_DIRECTORY_FILE, /* No FILE_SYNCHRONOUS_IO_NONALERT. */ + NULL /*EaBuffer*/, + 0 /*EaLength*/); + if (NT_SUCCESS(rcNt)) + rcNt = Ios.Status; + if (NT_SUCCESS(rcNt)) + { + /* + * Try read error info. + */ + size_t cchPrefix = strlen(pszPrefix); + if (cchPrefix + 3 < cbErrorInfo) + { + LARGE_INTEGER offRead; + offRead.QuadPart = 0; + rcNt = NtReadFile(hFile, NULL /*hEvent*/, NULL /*ApcRoutine*/, NULL /*ApcContext*/, &Ios, + &pszErrorInfo[cchPrefix], (ULONG)(cbErrorInfo - cchPrefix - 1), &offRead, NULL); + if (NT_SUCCESS(rcNt) && NT_SUCCESS(Ios.Status) && Ios.Information > 0) + { + memcpy(pszErrorInfo, pszPrefix, cchPrefix); + pszErrorInfo[RT_MIN(cbErrorInfo - 1, cchPrefix + Ios.Information)] = '\0'; + SUP_DPRINTF(("supR3HardenedWinReadErrorInfoDevice: '%s'", &pszErrorInfo[cchPrefix])); + } + else + { + *pszErrorInfo = '\0'; + if (rcNt != STATUS_END_OF_FILE || Ios.Status != STATUS_END_OF_FILE) + SUP_DPRINTF(("supR3HardenedWinReadErrorInfoDevice: NtReadFile -> %#x / %#x / %p\n", + rcNt, Ios.Status, Ios.Information)); + } + } + else + RTStrCopy(pszErrorInfo, cbErrorInfo, "error info buffer too small"); + NtClose(hFile); + } + else + SUP_DPRINTF(("supR3HardenedWinReadErrorInfoDevice: NtCreateFile -> %#x\n", rcNt)); + + return pszErrorInfo; +} + + + +/** + * Checks if the driver exists. + * + * This checks whether the driver is present in the /Driver object directory. + * Drivers being initialized or terminated will have an object there + * before/after their devices nodes are created/deleted. + * + * @returns true if it exists, false if not. + * @param pszDriver The driver name. + */ +static bool supR3HardenedWinDriverExists(const char *pszDriver) +{ + /* + * Open the driver object directory. + */ + UNICODE_STRING NtDirName = RTNT_CONSTANT_UNISTR(L"\\Driver"); + + OBJECT_ATTRIBUTES ObjAttr; + InitializeObjectAttributes(&ObjAttr, &NtDirName, OBJ_CASE_INSENSITIVE, NULL /*hRootDir*/, NULL /*pSecDesc*/); + + HANDLE hDir; + NTSTATUS rcNt = NtOpenDirectoryObject(&hDir, DIRECTORY_QUERY | FILE_LIST_DIRECTORY, &ObjAttr); +#ifdef VBOX_STRICT + SUPR3HARDENED_ASSERT_NT_SUCCESS(rcNt); +#endif + if (!NT_SUCCESS(rcNt)) + return true; + + /* + * Enumerate it, looking for the driver. + */ + bool fFound = true; + ULONG uObjDirCtx = 0; + do + { + uint32_t abBuffer[_64K + _1K]; + ULONG cbActual; + rcNt = NtQueryDirectoryObject(hDir, + abBuffer, + sizeof(abBuffer) - 4, /* minus four for string terminator space. */ + FALSE /*ReturnSingleEntry */, + FALSE /*RestartScan*/, + &uObjDirCtx, + &cbActual); + if (!NT_SUCCESS(rcNt) || cbActual < sizeof(OBJECT_DIRECTORY_INFORMATION)) + break; + + POBJECT_DIRECTORY_INFORMATION pObjDir = (POBJECT_DIRECTORY_INFORMATION)abBuffer; + while (pObjDir->Name.Length != 0) + { + WCHAR wcSaved = pObjDir->Name.Buffer[pObjDir->Name.Length / sizeof(WCHAR)]; + pObjDir->Name.Buffer[pObjDir->Name.Length / sizeof(WCHAR)] = '\0'; + if ( pObjDir->Name.Length > 1 + && RTUtf16ICmpAscii(pObjDir->Name.Buffer, pszDriver) == 0) + { + fFound = true; + break; + } + pObjDir->Name.Buffer[pObjDir->Name.Length / sizeof(WCHAR)] = wcSaved; + + /* Next directory entry. */ + pObjDir++; + } + } while (!fFound); + + /* + * Clean up and return. + */ + NtClose(hDir); + + return fFound; +} + + +/** + * Open the stub device before the 2nd respawn. + */ +static void supR3HardenedWinOpenStubDevice(void) +{ + if (g_fSupStubOpened) + return; + + /* + * Retry if we think driver might still be initializing (STATUS_NO_SUCH_DEVICE + \Drivers\VBoxDrv). + */ + static const WCHAR s_wszName[] = SUPDRV_NT_DEVICE_NAME_STUB; + uint64_t const uMsTsStart = supR3HardenedWinGetMilliTS(); + NTSTATUS rcNt; + uint32_t iTry; + + for (iTry = 0;; iTry++) + { + HANDLE hFile = RTNT_INVALID_HANDLE_VALUE; + IO_STATUS_BLOCK Ios = RTNT_IO_STATUS_BLOCK_INITIALIZER; + + UNICODE_STRING NtName; + NtName.Buffer = (PWSTR)s_wszName; + NtName.Length = sizeof(s_wszName) - sizeof(WCHAR); + NtName.MaximumLength = sizeof(s_wszName); + + OBJECT_ATTRIBUTES ObjAttr; + InitializeObjectAttributes(&ObjAttr, &NtName, OBJ_CASE_INSENSITIVE, NULL /*hRootDir*/, NULL /*pSecDesc*/); + + rcNt = NtCreateFile(&hFile, + GENERIC_READ | GENERIC_WRITE, /* No SYNCHRONIZE. */ + &ObjAttr, + &Ios, + NULL /* Allocation Size*/, + FILE_ATTRIBUTE_NORMAL, + FILE_SHARE_READ | FILE_SHARE_WRITE, + FILE_OPEN, + FILE_NON_DIRECTORY_FILE, /* No FILE_SYNCHRONOUS_IO_NONALERT. */ + NULL /*EaBuffer*/, + 0 /*EaLength*/); + if (NT_SUCCESS(rcNt)) + rcNt = Ios.Status; + + /* The STATUS_NO_SUCH_DEVICE might be returned if the device is not + completely initialized. Delay a little bit and try again. */ + if (rcNt != STATUS_NO_SUCH_DEVICE) + break; + if (iTry > 0 && supR3HardenedWinGetMilliTS() - uMsTsStart > 5000) /* 5 sec, at least two tries */ + break; + if (!supR3HardenedWinDriverExists("VBoxDrv")) + { + /** @todo Consider starting the VBoxdrv.sys service. Requires 2nd process + * though, rather complicated actually as CreateProcess causes all + * kind of things to happen to this process which would make it hard to + * pass the process verification tests... :-/ */ + break; + } + + LARGE_INTEGER Time; + if (iTry < 8) + Time.QuadPart = -1000000 / 100; /* 1ms in 100ns units, relative time. */ + else + Time.QuadPart = -32000000 / 100; /* 32ms in 100ns units, relative time. */ + NtDelayExecution(TRUE, &Time); + } + + if (NT_SUCCESS(rcNt)) + g_fSupStubOpened = true; + else + { + /* + * Report trouble (fatal). For some errors codes we try gather some + * extra information that goes into VBoxStartup.log so that we stand a + * better chance resolving the issue. + */ + char szErrorInfo[16384]; + int rc = VERR_OPEN_FAILED; + if (SUP_NT_STATUS_IS_VBOX(rcNt)) /* See VBoxDrvNtErr2NtStatus. */ + { + rc = SUP_NT_STATUS_TO_VBOX(rcNt); + + /* + * \Windows\ApiPort open trouble. So far only + * STATUS_OBJECT_TYPE_MISMATCH has been observed. + */ + if (rc == VERR_SUPDRV_APIPORT_OPEN_ERROR) + { + SUP_DPRINTF(("Error opening VBoxDrvStub: VERR_SUPDRV_APIPORT_OPEN_ERROR\n")); + + uint32_t uSessionId = NtCurrentPeb()->SessionId; + SUP_DPRINTF((" SessionID=%#x\n", uSessionId)); + char szDir[64]; + if (uSessionId == 0) + RTStrCopy(szDir, sizeof(szDir), "\\Windows"); + else + { + RTStrPrintf(szDir, sizeof(szDir), "\\Sessions\\%u\\Windows", uSessionId); + supR3HardenedWinLogObjDir(szDir); + } + supR3HardenedWinLogObjDir("\\Windows"); + supR3HardenedWinLogObjDir("\\Sessions"); + + supR3HardenedFatalMsg("supR3HardenedWinReSpawn", kSupInitOp_Misc, rc, + "NtCreateFile(%ls) failed: VERR_SUPDRV_APIPORT_OPEN_ERROR\n" + "\n" + "Error getting %s\\ApiPort in the driver from vboxsup.\n" + "\n" + "Could be due to security software is redirecting access to it, so please include full " + "details of such software in a bug report. VBoxStartup.log may contain details important " + "to resolving the issue.%s" + , s_wszName, szDir, + supR3HardenedWinReadErrorInfoDevice(szErrorInfo, sizeof(szErrorInfo), + "\n\nVBoxDrvStub error: ")); + } + + /* + * Generic VBox failure message. + */ + supR3HardenedFatalMsg("supR3HardenedWinReSpawn", kSupInitOp_Driver, rc, + "NtCreateFile(%ls) failed: %Rrc (rcNt=%#x)%s", s_wszName, rc, rcNt, + supR3HardenedWinReadErrorInfoDevice(szErrorInfo, sizeof(szErrorInfo), + "\nVBoxDrvStub error: ")); + } + else + { + const char *pszDefine; + switch (rcNt) + { + case STATUS_NO_SUCH_DEVICE: pszDefine = " STATUS_NO_SUCH_DEVICE"; break; + case STATUS_OBJECT_NAME_NOT_FOUND: pszDefine = " STATUS_OBJECT_NAME_NOT_FOUND"; break; + case STATUS_ACCESS_DENIED: pszDefine = " STATUS_ACCESS_DENIED"; break; + case STATUS_TRUST_FAILURE: pszDefine = " STATUS_TRUST_FAILURE"; break; + default: pszDefine = ""; break; + } + + /* + * Problems opening the device is generally due to driver load/ + * unload issues. Check whether the driver is loaded and make + * suggestions accordingly. + */ +/** @todo don't fail during early init, wait till later and try load the driver if missing or at least query the service manager for additional information. */ + if ( rcNt == STATUS_NO_SUCH_DEVICE + || rcNt == STATUS_OBJECT_NAME_NOT_FOUND) + { + SUP_DPRINTF(("Error opening VBoxDrvStub: %s\n", pszDefine)); + if (supR3HardenedWinDriverExists("VBoxDrv")) + supR3HardenedFatalMsg("supR3HardenedWinReSpawn", kSupInitOp_Driver, VERR_OPEN_FAILED, + "NtCreateFile(%ls) failed: %#x%s (%u retries)\n" + "\n" + "Driver is probably stuck stopping/starting. Try 'sc.exe query vboxsup' to get more " + "information about its state. Rebooting may actually help.%s" + , s_wszName, rcNt, pszDefine, iTry, + supR3HardenedWinReadErrorInfoDevice(szErrorInfo, sizeof(szErrorInfo), + "\nVBoxDrvStub error: ")); + else + supR3HardenedFatalMsg("supR3HardenedWinReSpawn", kSupInitOp_Driver, VERR_OPEN_FAILED, + "NtCreateFile(%ls) failed: %#x%s (%u retries)\n" + "\n" + "Driver is does not appear to be loaded. Try 'sc.exe start vboxsup', reinstall " + "VirtualBox or reboot.%s" + , s_wszName, rcNt, pszDefine, iTry, + supR3HardenedWinReadErrorInfoDevice(szErrorInfo, sizeof(szErrorInfo), + "\nVBoxDrvStub error: ")); + } + + /* Generic NT failure message. */ + supR3HardenedFatalMsg("supR3HardenedWinReSpawn", kSupInitOp_Driver, VERR_OPEN_FAILED, + "NtCreateFile(%ls) failed: %#x%s (%u retries)%s", + s_wszName, rcNt, pszDefine, iTry, + supR3HardenedWinReadErrorInfoDevice(szErrorInfo, sizeof(szErrorInfo), + "\nVBoxDrvStub error: ")); + } + } +} + + +/** + * Called by the main code if supR3HardenedWinIsReSpawnNeeded returns @c true. + * + * @returns Program exit code. + */ +DECLHIDDEN(int) supR3HardenedWinReSpawn(int iWhich) +{ + /* + * Before the 2nd respawn we set up a child protection deal with the + * support driver via /Devices/VBoxDrvStub. (We tried to do this + * during the early init, but in case we had trouble accessing vboxdrv + * (renamed to vboxsup in 7.0 and 6.1.34) we retry it here where we + * have kernel32.dll and others to pull in for better diagnostics.) + */ + if (iWhich == 2) + supR3HardenedWinOpenStubDevice(); + + /* + * Make sure we're alone in the stub process before creating the VM process + * and that there aren't any debuggers attached. + */ + if (iWhich == 2) + { + int rc = supHardNtVpDebugger(NtCurrentProcess(), RTErrInfoInitStatic(&g_ErrInfoStatic)); + if (RT_SUCCESS(rc)) + rc = supHardNtVpThread(NtCurrentProcess(), NtCurrentThread(), RTErrInfoInitStatic(&g_ErrInfoStatic)); + if (RT_FAILURE(rc)) + supR3HardenedFatalMsg("supR3HardenedWinReSpawn", kSupInitOp_Integrity, rc, "%s", g_ErrInfoStatic.szMsg); + } + + + /* + * Respawn the process with kernel protection for the new process. + */ + supR3HardenedWinDoReSpawn(iWhich); + /* not reached! */ +} + + +/** + * Checks if re-spawning is required, replacing the respawn argument if not. + * + * @returns true if required, false if not. In the latter case, the first + * argument in the vector is replaced. + * @param iWhich Which respawn we're to check for, 1 being the + * first one, and 2 the second and final. + * @param cArgs The number of arguments. + * @param papszArgs Pointer to the argument vector. + */ +DECLHIDDEN(bool) supR3HardenedWinIsReSpawnNeeded(int iWhich, int cArgs, char **papszArgs) +{ + SUPR3HARDENED_ASSERT(g_cSuplibHardenedWindowsMainCalls == 1); + SUPR3HARDENED_ASSERT(iWhich == 1 || iWhich == 2); + + if (cArgs < 1) + return true; + + if (suplibHardenedStrCmp(papszArgs[0], SUPR3_RESPAWN_1_ARG0) == 0) + { + if (iWhich > 1) + return true; + } + else if (suplibHardenedStrCmp(papszArgs[0], SUPR3_RESPAWN_2_ARG0) == 0) + { + if (iWhich < 2) + return false; + } + else + return true; + + /* Replace the argument. */ + papszArgs[0] = g_szSupLibHardenedExePath; + return false; +} + + +/** + * Initializes the windows verficiation bits and other things we're better off + * doing after main() has passed on it's data. + * + * @param fFlags The main flags. + * @param fAvastKludge Whether to apply the avast kludge. + */ +DECLHIDDEN(void) supR3HardenedWinInit(uint32_t fFlags, bool fAvastKludge) +{ + NTSTATUS rcNt; + +#ifndef VBOX_WITHOUT_DEBUGGER_CHECKS + /* + * Install a anti debugging hack before we continue. This prevents most + * notifications from ending up in the debugger. (Also applied to the + * child process when respawning.) + */ + rcNt = NtSetInformationThread(NtCurrentThread(), ThreadHideFromDebugger, NULL, 0); + if (!NT_SUCCESS(rcNt)) + supR3HardenedFatalMsg("supR3HardenedWinInit", kSupInitOp_Misc, VERR_GENERAL_FAILURE, + "NtSetInformationThread/ThreadHideFromDebugger failed: %#x\n", rcNt); +#endif + + /* + * Init the verifier. + */ + RTErrInfoInitStatic(&g_ErrInfoStatic); + int rc = supHardenedWinInitImageVerifier(&g_ErrInfoStatic.Core); + if (RT_FAILURE(rc)) + supR3HardenedFatalMsg("supR3HardenedWinInit", kSupInitOp_Misc, rc, + "supHardenedWinInitImageVerifier failed: %s", g_ErrInfoStatic.szMsg); + + /* + * Get the windows system directory from the KnownDlls dir. + */ + HANDLE hSymlink = INVALID_HANDLE_VALUE; + UNICODE_STRING UniStr = RTNT_CONSTANT_UNISTR(L"\\KnownDlls\\KnownDllPath"); + OBJECT_ATTRIBUTES ObjAttrs; + InitializeObjectAttributes(&ObjAttrs, &UniStr, OBJ_CASE_INSENSITIVE, NULL /*hRootDir*/, NULL /*pSecDesc*/); + rcNt = NtOpenSymbolicLinkObject(&hSymlink, SYMBOLIC_LINK_QUERY, &ObjAttrs); + if (!NT_SUCCESS(rcNt)) + supR3HardenedFatalMsg("supR3HardenedWinInit", kSupInitOp_Misc, rcNt, "Error opening '%ls': %#x", UniStr.Buffer, rcNt); + + g_System32WinPath.UniStr.Buffer = g_System32WinPath.awcBuffer; + g_System32WinPath.UniStr.Length = 0; + g_System32WinPath.UniStr.MaximumLength = sizeof(g_System32WinPath.awcBuffer) - sizeof(RTUTF16); + rcNt = NtQuerySymbolicLinkObject(hSymlink, &g_System32WinPath.UniStr, NULL); + if (!NT_SUCCESS(rcNt)) + supR3HardenedFatalMsg("supR3HardenedWinInit", kSupInitOp_Misc, rcNt, "Error querying '%ls': %#x", UniStr.Buffer, rcNt); + g_System32WinPath.UniStr.Buffer[g_System32WinPath.UniStr.Length / sizeof(RTUTF16)] = '\0'; + + SUP_DPRINTF(("KnownDllPath: %ls\n", g_System32WinPath.UniStr.Buffer)); + NtClose(hSymlink); + + if (!(fFlags & SUPSECMAIN_FLAGS_DONT_OPEN_DEV)) + { + if (fAvastKludge) + { + /* + * Do a self purification to cure avast's weird NtOpenFile write-thru + * change in GetBinaryTypeW change in kernel32. Unfortunately, avast + * uses a system thread to perform the process modifications, which + * means it's hard to make sure it had the chance to make them... + * + * We have to resort to kludge doing yield and sleep fudging for a + * number of milliseconds and schedulings before we can hope that avast + * and similar products have done what they need to do. If we do any + * fixes, we wait for a while again and redo it until we're clean. + * + * This is unfortunately kind of fragile. + */ + uint32_t cMsFudge = g_fSupAdversaries ? 512 : 128; + uint32_t cFixes; + for (uint32_t iLoop = 0; iLoop < 16; iLoop++) + { + uint32_t cSleeps = 0; + uint64_t uMsTsStart = supR3HardenedWinGetMilliTS(); + do + { + NtYieldExecution(); + LARGE_INTEGER Time; + Time.QuadPart = -8000000 / 100; /* 8ms in 100ns units, relative time. */ + NtDelayExecution(FALSE, &Time); + cSleeps++; + } while ( supR3HardenedWinGetMilliTS() - uMsTsStart <= cMsFudge + || cSleeps < 8); + SUP_DPRINTF(("supR3HardenedWinInit: Startup delay kludge #2/%u: %u ms, %u sleeps\n", + iLoop, supR3HardenedWinGetMilliTS() - uMsTsStart, cSleeps)); + + cFixes = 0; + rc = supHardenedWinVerifyProcess(NtCurrentProcess(), NtCurrentThread(), SUPHARDNTVPKIND_SELF_PURIFICATION, + 0 /*fFlags*/, &cFixes, NULL /*pErrInfo*/); + if (RT_FAILURE(rc) || cFixes == 0) + break; + + if (!g_fSupAdversaries) + g_fSupAdversaries |= SUPHARDNT_ADVERSARY_UNKNOWN; + cMsFudge = 512; + + /* Log the KiOpPrefetchPatchCount value if available, hoping it might sched some light on spider38's case. */ + ULONG cPatchCount = 0; + rcNt = NtQuerySystemInformation(SystemInformation_KiOpPrefetchPatchCount, + &cPatchCount, sizeof(cPatchCount), NULL); + if (NT_SUCCESS(rcNt)) + SUP_DPRINTF(("supR3HardenedWinInit: cFixes=%u g_fSupAdversaries=%#x cPatchCount=%#u\n", + cFixes, g_fSupAdversaries, cPatchCount)); + else + SUP_DPRINTF(("supR3HardenedWinInit: cFixes=%u g_fSupAdversaries=%#x\n", cFixes, g_fSupAdversaries)); + } + } + + /* + * Install the hooks. + */ + supR3HardenedWinInstallHooks(); + } + else if (fFlags & SUPSECMAIN_FLAGS_FIRST_PROCESS) + { + /* + * Try shake anyone (e.g. easyhook) patching process creation code in + * kernelbase, kernel32 or ntdll so they won't so easily cause the child + * to crash when we respawn and purify it. + */ + SUP_DPRINTF(("supR3HardenedWinInit: Performing a limited self purification...\n")); + uint32_t cFixes = 0; + rc = supHardenedWinVerifyProcess(NtCurrentProcess(), NtCurrentThread(), SUPHARDNTVPKIND_SELF_PURIFICATION_LIMITED, + 0 /*fFlags*/, &cFixes, NULL /*pErrInfo*/); + SUP_DPRINTF(("supR3HardenedWinInit: SUPHARDNTVPKIND_SELF_PURIFICATION_LIMITED -> %Rrc, cFixes=%d\n", rc, cFixes)); + RT_NOREF(rc); /* ignored on purpose */ + } + +#ifndef VBOX_WITH_VISTA_NO_SP + /* + * Complain about Vista w/o service pack if we're launching a VM. + */ + if ( !(fFlags & SUPSECMAIN_FLAGS_DONT_OPEN_DEV) + && g_uNtVerCombined >= SUP_NT_VER_VISTA + && g_uNtVerCombined < SUP_MAKE_NT_VER_COMBINED(6, 0, 6001, 0, 0)) + supR3HardenedFatalMsg("supR3HardenedWinInit", kSupInitOp_Misc, VERR_NOT_SUPPORTED, + "Window Vista without any service pack installed is not supported. Please install the latest service pack."); +#endif +} + + +/** + * Modifies the DLL search path for testcases. + * + * This makes sure the application binary path is in the search path. When + * starting a testcase executable in the testcase/ subdirectory this isn't the + * case by default. So, unless we do something about it we won't be able to + * import VBox DLLs. + * + * @param fFlags The main flags (giving the location). + * @param pszAppBinPath The path to the application binary directory + * (windows style). + */ +DECLHIDDEN(void) supR3HardenedWinModifyDllSearchPath(uint32_t fFlags, const char *pszAppBinPath) +{ + /* + * For the testcases to work, we must add the app bin directory to the + * DLL search list before the testcase dll is loaded or it won't be + * able to find the VBox DLLs. This is done _after_ VBoxRT.dll is + * initialized and sets its defaults. + */ + switch (fFlags & SUPSECMAIN_FLAGS_LOC_MASK) + { + case SUPSECMAIN_FLAGS_LOC_TESTCASE: + break; + default: + return; + } + + /* + * Dynamically resolve the two APIs we need (the latter uses forwarders on w7). + */ + HMODULE hModKernel32 = GetModuleHandleW(L"kernel32.dll"); + + typedef BOOL (WINAPI *PFNSETDLLDIRECTORY)(LPCWSTR); + PFNSETDLLDIRECTORY pfnSetDllDir; + pfnSetDllDir = (PFNSETDLLDIRECTORY)GetProcAddress(hModKernel32, "SetDllDirectoryW"); + + typedef BOOL (WINAPI *PFNSETDEFAULTDLLDIRECTORIES)(DWORD); + PFNSETDEFAULTDLLDIRECTORIES pfnSetDefDllDirs; + pfnSetDefDllDirs = (PFNSETDEFAULTDLLDIRECTORIES)GetProcAddress(hModKernel32, "SetDefaultDllDirectories"); + + if (pfnSetDllDir != NULL) + { + /* + * Convert the path to UTF-16 and try set it. + */ + PRTUTF16 pwszAppBinPath = NULL; + int rc = RTStrToUtf16(pszAppBinPath, &pwszAppBinPath); + if (RT_SUCCESS(rc)) + { + if (pfnSetDllDir(pwszAppBinPath)) + { + SUP_DPRINTF(("supR3HardenedWinModifyDllSearchPath: Set dll dir to '%ls'\n", pwszAppBinPath)); + g_fSupLibHardenedDllSearchUserDirs = true; + + /* + * We set it alright, on W7 and later we also must modify the + * default DLL search order. See @bugref{6861} for details on + * why we don't do this on Vista (also see init-win.cpp in IPRT). + */ + if ( pfnSetDefDllDirs + && g_uNtVerCombined >= SUP_NT_VER_W70) + { + if (pfnSetDefDllDirs( LOAD_LIBRARY_SEARCH_APPLICATION_DIR + | LOAD_LIBRARY_SEARCH_SYSTEM32 + | LOAD_LIBRARY_SEARCH_USER_DIRS)) + SUP_DPRINTF(("supR3HardenedWinModifyDllSearchPath: Successfully modified search dirs.\n")); + else + supR3HardenedFatal("supR3HardenedWinModifyDllSearchPath: SetDllDirectoryW(%ls) failed: %d\n", + pwszAppBinPath, RtlGetLastWin32Error()); + } + } + else + supR3HardenedFatal("supR3HardenedWinModifyDllSearchPath: SetDllDirectoryW(%ls) failed: %d\n", + pwszAppBinPath, RtlGetLastWin32Error()); + RTUtf16Free(pwszAppBinPath); + } + else + supR3HardenedFatal("supR3HardenedWinModifyDllSearchPath: RTStrToUtf16(%s) failed: %d\n", pszAppBinPath, rc); + } +} + + +/** + * Initializes the application binary directory path. + * + * This is called once or twice. + * + * @param fFlags The main flags (giving the location). + */ +DECLHIDDEN(void) supR3HardenedWinInitAppBin(uint32_t fFlags) +{ + USHORT cwc = (USHORT)g_offSupLibHardenedExeNtName - 1; + g_SupLibHardenedAppBinNtPath.UniStr.Buffer = g_SupLibHardenedAppBinNtPath.awcBuffer; + memcpy(g_SupLibHardenedAppBinNtPath.UniStr.Buffer, g_SupLibHardenedExeNtPath.UniStr.Buffer, cwc * sizeof(WCHAR)); + + switch (fFlags & SUPSECMAIN_FLAGS_LOC_MASK) + { + case SUPSECMAIN_FLAGS_LOC_APP_BIN: + break; + case SUPSECMAIN_FLAGS_LOC_TESTCASE: + { + /* Drop one directory level. */ + USHORT off = cwc; + WCHAR wc; + while ( off > 1 + && (wc = g_SupLibHardenedAppBinNtPath.UniStr.Buffer[off - 1]) != '\0') + if (wc != '\\' && wc != '/') + off--; + else + { + if (g_SupLibHardenedAppBinNtPath.UniStr.Buffer[off - 2] == ':') + cwc = off; + else + cwc = off - 1; + break; + } + break; + } + default: + supR3HardenedFatal("supR3HardenedWinInitAppBin: Unknown program binary location: %#x\n", fFlags); + } + + g_SupLibHardenedAppBinNtPath.UniStr.Buffer[cwc] = '\0'; + g_SupLibHardenedAppBinNtPath.UniStr.Length = cwc * sizeof(WCHAR); + g_SupLibHardenedAppBinNtPath.UniStr.MaximumLength = sizeof(g_SupLibHardenedAppBinNtPath.awcBuffer); + SUP_DPRINTF(("supR3HardenedWinInitAppBin(%#x): '%ls'\n", fFlags, g_SupLibHardenedAppBinNtPath.UniStr.Buffer)); +} + + +/** + * Converts the Windows command line string (UTF-16) to an array of UTF-8 + * arguments suitable for passing to main(). + * + * @returns Pointer to the argument array. + * @param pawcCmdLine The UTF-16 windows command line to parse. + * @param cwcCmdLine The length of the command line. + * @param pcArgs Where to return the number of arguments. + */ +static char **suplibCommandLineToArgvWStub(PCRTUTF16 pawcCmdLine, size_t cwcCmdLine, int *pcArgs) +{ + /* + * Convert the command line string to UTF-8. + */ + char *pszCmdLine = NULL; + SUPR3HARDENED_ASSERT(RT_SUCCESS(RTUtf16ToUtf8Ex(pawcCmdLine, cwcCmdLine, &pszCmdLine, 0, NULL))); + + /* + * Parse the command line, carving argument strings out of it. + */ + int cArgs = 0; + int cArgsAllocated = 4; + char **papszArgs = (char **)RTMemAllocZ(sizeof(char *) * cArgsAllocated); + char *pszSrc = pszCmdLine; + for (;;) + { + /* skip leading blanks. */ + char ch = *pszSrc; + while (suplibCommandLineIsArgSeparator(ch)) + ch = *++pszSrc; + if (!ch) + break; + + /* Add argument to the vector. */ + if (cArgs + 2 >= cArgsAllocated) + { + cArgsAllocated *= 2; + papszArgs = (char **)RTMemRealloc(papszArgs, sizeof(char *) * cArgsAllocated); + } + papszArgs[cArgs++] = pszSrc; + papszArgs[cArgs] = NULL; + + /* Unquote and unescape the string. */ + char *pszDst = pszSrc++; + bool fQuoted = false; + do + { + if (ch == '"') + fQuoted = !fQuoted; + else if (ch != '\\' || (*pszSrc != '\\' && *pszSrc != '"')) + *pszDst++ = ch; + else + { + unsigned cSlashes = 0; + while ((ch = *pszSrc++) == '\\') + cSlashes++; + if (ch == '"') + { + while (cSlashes >= 2) + { + cSlashes -= 2; + *pszDst++ = '\\'; + } + if (cSlashes) + *pszDst++ = '"'; + else + fQuoted = !fQuoted; + } + else + { + pszSrc--; + while (cSlashes-- > 0) + *pszDst++ = '\\'; + } + } + + ch = *pszSrc++; + } while (ch != '\0' && (fQuoted || !suplibCommandLineIsArgSeparator(ch))); + + /* Terminate the argument. */ + *pszDst = '\0'; + if (!ch) + break; + } + + *pcArgs = cArgs; + return papszArgs; +} + + +/** + * Worker for supR3HardenedFindVersionRsrcOffset. + * + * @returns RVA the version resource data, UINT32_MAX if not found. + * @param pRootDir The root resource directory. Expects data to + * follow. + * @param cbBuf The amount of data at pRootDir. + * @param offData The offset to the data entry. + * @param pcbData Where to return the size of the data. + */ +static uint32_t supR3HardenedGetRvaFromRsrcDataEntry(PIMAGE_RESOURCE_DIRECTORY pRootDir, uint32_t cbBuf, uint32_t offData, + uint32_t *pcbData) +{ + if ( offData <= cbBuf + && offData + sizeof(IMAGE_RESOURCE_DATA_ENTRY) <= cbBuf) + { + PIMAGE_RESOURCE_DATA_ENTRY pRsrcData = (PIMAGE_RESOURCE_DATA_ENTRY)((uintptr_t)pRootDir + offData); + SUP_DPRINTF((" [Raw version resource data: %#x LB %#x, codepage %#x (reserved %#x)]\n", + pRsrcData->OffsetToData, pRsrcData->Size, pRsrcData->CodePage, pRsrcData->Reserved)); + if (pRsrcData->Size > 0) + { + *pcbData = pRsrcData->Size; + return pRsrcData->OffsetToData; + } + } + else + SUP_DPRINTF((" Version resource data (%#x) is outside the buffer (%#x)! :-(\n", offData, cbBuf)); + + *pcbData = 0; + return UINT32_MAX; +} + + +/** @def SUP_RSRC_DPRINTF + * Dedicated debug printf for resource directory parsing. + * @sa SUP_DPRINTF + */ +#if 0 /* more details */ +# define SUP_RSRC_DPRINTF(a) SUP_DPRINTF(a) +#else +# define SUP_RSRC_DPRINTF(a) do { } while (0) +#endif + +/** + * Scans the resource directory for a version resource. + * + * @returns RVA of the version resource data, UINT32_MAX if not found. + * @param pRootDir The root resource directory. Expects data to + * follow. + * @param cbBuf The amount of data at pRootDir. + * @param pcbData Where to return the size of the version data. + */ +static uint32_t supR3HardenedFindVersionRsrcRva(PIMAGE_RESOURCE_DIRECTORY pRootDir, uint32_t cbBuf, uint32_t *pcbData) +{ + SUP_RSRC_DPRINTF((" ResDir: Char=%#x Time=%#x Ver=%d%d #NamedEntries=%#x #IdEntries=%#x\n", + pRootDir->Characteristics, + pRootDir->TimeDateStamp, + pRootDir->MajorVersion, + pRootDir->MinorVersion, + pRootDir->NumberOfNamedEntries, + pRootDir->NumberOfIdEntries)); + + PIMAGE_RESOURCE_DIRECTORY_ENTRY paEntries = (PIMAGE_RESOURCE_DIRECTORY_ENTRY)(pRootDir + 1); + unsigned cMaxEntries = (cbBuf - sizeof(IMAGE_RESOURCE_DIRECTORY)) / sizeof(IMAGE_RESOURCE_DIRECTORY_ENTRY); + unsigned cEntries = pRootDir->NumberOfNamedEntries + pRootDir->NumberOfIdEntries; + if (cEntries > cMaxEntries) + cEntries = cMaxEntries; + for (unsigned i = 0; i < cEntries; i++) + { + if (!paEntries[i].NameIsString) + { + if (!paEntries[i].DataIsDirectory) + SUP_RSRC_DPRINTF((" #%u: ID: #%#06x Data: %#010x\n", + i, paEntries[i].Id, paEntries[i].OffsetToData)); + else + SUP_RSRC_DPRINTF((" #%u: ID: #%#06x Dir: %#010x\n", + i, paEntries[i].Id, paEntries[i].OffsetToDirectory)); + } + else + { + if (!paEntries[i].DataIsDirectory) + SUP_RSRC_DPRINTF((" #%u: Name: #%#06x Data: %#010x\n", + i, paEntries[i].NameOffset, paEntries[i].OffsetToData)); + else + SUP_RSRC_DPRINTF((" #%u: Name: #%#06x Dir: %#010x\n", + i, paEntries[i].NameOffset, paEntries[i].OffsetToDirectory)); + } + + /* + * Look for the version resource type. Skip to the next entry if not found. + */ + if (paEntries[i].NameIsString) + continue; + if (paEntries[i].Id != 0x10 /*RT_VERSION*/) + continue; + if (!paEntries[i].DataIsDirectory) + { + SUP_DPRINTF((" #%u: ID: #%#06x Data: %#010x - WEIRD!\n", i, paEntries[i].Id, paEntries[i].OffsetToData)); + continue; + } + SUP_RSRC_DPRINTF((" Version resource dir entry #%u: dir offset: %#x (cbBuf=%#x)\n", + i, paEntries[i].OffsetToDirectory, cbBuf)); + + /* + * Locate the sub-resource directory for it. + */ + if (paEntries[i].OffsetToDirectory >= cbBuf) + { + SUP_DPRINTF((" Version resource dir is outside the buffer! :-(\n")); + continue; + } + uint32_t cbMax = cbBuf - paEntries[i].OffsetToDirectory; + if (cbMax < sizeof(IMAGE_RESOURCE_DIRECTORY) + sizeof(IMAGE_RESOURCE_DIRECTORY_ENTRY)) + { + SUP_DPRINTF((" Version resource dir entry #0 is outside the buffer! :-(\n")); + continue; + } + PIMAGE_RESOURCE_DIRECTORY pVerDir = (PIMAGE_RESOURCE_DIRECTORY)((uintptr_t)pRootDir + paEntries[i].OffsetToDirectory); + SUP_RSRC_DPRINTF((" VerDir: Char=%#x Time=%#x Ver=%d%d #NamedEntries=%#x #IdEntries=%#x\n", + pVerDir->Characteristics, + pVerDir->TimeDateStamp, + pVerDir->MajorVersion, + pVerDir->MinorVersion, + pVerDir->NumberOfNamedEntries, + pVerDir->NumberOfIdEntries)); + PIMAGE_RESOURCE_DIRECTORY_ENTRY paVerEntries = (PIMAGE_RESOURCE_DIRECTORY_ENTRY)(pVerDir + 1); + unsigned cMaxVerEntries = (cbMax - sizeof(IMAGE_RESOURCE_DIRECTORY)) / sizeof(IMAGE_RESOURCE_DIRECTORY_ENTRY); + unsigned cVerEntries = pVerDir->NumberOfNamedEntries + pVerDir->NumberOfIdEntries; + if (cVerEntries > cMaxVerEntries) + cVerEntries = cMaxVerEntries; + for (unsigned iVer = 0; iVer < cVerEntries; iVer++) + { + if (!paVerEntries[iVer].NameIsString) + { + if (!paVerEntries[iVer].DataIsDirectory) + SUP_RSRC_DPRINTF((" #%u: ID: #%#06x Data: %#010x\n", + iVer, paVerEntries[iVer].Id, paVerEntries[iVer].OffsetToData)); + else + SUP_RSRC_DPRINTF((" #%u: ID: #%#06x Dir: %#010x\n", + iVer, paVerEntries[iVer].Id, paVerEntries[iVer].OffsetToDirectory)); + } + else + { + if (!paVerEntries[iVer].DataIsDirectory) + SUP_RSRC_DPRINTF((" #%u: Name: #%#06x Data: %#010x\n", + iVer, paVerEntries[iVer].NameOffset, paVerEntries[iVer].OffsetToData)); + else + SUP_RSRC_DPRINTF((" #%u: Name: #%#06x Dir: %#010x\n", + iVer, paVerEntries[iVer].NameOffset, paVerEntries[iVer].OffsetToDirectory)); + } + if (!paVerEntries[iVer].DataIsDirectory) + { + SUP_DPRINTF((" [Version info resource found at %#x! (ID/Name: #%#x)]\n", + paVerEntries[iVer].OffsetToData, paVerEntries[iVer].Name)); + return supR3HardenedGetRvaFromRsrcDataEntry(pRootDir, cbBuf, paVerEntries[iVer].OffsetToData, pcbData); + } + + /* + * Check out the next directory level. + */ + if (paVerEntries[iVer].OffsetToDirectory >= cbBuf) + { + SUP_DPRINTF((" Version resource subdir is outside the buffer! :-(\n")); + continue; + } + cbMax = cbBuf - paVerEntries[iVer].OffsetToDirectory; + if (cbMax < sizeof(IMAGE_RESOURCE_DIRECTORY) + sizeof(IMAGE_RESOURCE_DIRECTORY_ENTRY)) + { + SUP_DPRINTF((" Version resource subdir entry #0 is outside the buffer! :-(\n")); + continue; + } + PIMAGE_RESOURCE_DIRECTORY pVerSubDir = (PIMAGE_RESOURCE_DIRECTORY)((uintptr_t)pRootDir + paVerEntries[iVer].OffsetToDirectory); + SUP_RSRC_DPRINTF((" VerSubDir#%u: Char=%#x Time=%#x Ver=%d%d #NamedEntries=%#x #IdEntries=%#x\n", + iVer, + pVerSubDir->Characteristics, + pVerSubDir->TimeDateStamp, + pVerSubDir->MajorVersion, + pVerSubDir->MinorVersion, + pVerSubDir->NumberOfNamedEntries, + pVerSubDir->NumberOfIdEntries)); + PIMAGE_RESOURCE_DIRECTORY_ENTRY paVerSubEntries = (PIMAGE_RESOURCE_DIRECTORY_ENTRY)(pVerSubDir + 1); + unsigned cMaxVerSubEntries = (cbMax - sizeof(IMAGE_RESOURCE_DIRECTORY)) / sizeof(IMAGE_RESOURCE_DIRECTORY_ENTRY); + unsigned cVerSubEntries = pVerSubDir->NumberOfNamedEntries + pVerSubDir->NumberOfIdEntries; + if (cVerSubEntries > cMaxVerSubEntries) + cVerSubEntries = cMaxVerSubEntries; + for (unsigned iVerSub = 0; iVerSub < cVerSubEntries; iVerSub++) + { + if (!paVerSubEntries[iVerSub].NameIsString) + { + if (!paVerSubEntries[iVerSub].DataIsDirectory) + SUP_RSRC_DPRINTF((" #%u: ID: #%#06x Data: %#010x\n", + iVerSub, paVerSubEntries[iVerSub].Id, paVerSubEntries[iVerSub].OffsetToData)); + else + SUP_RSRC_DPRINTF((" #%u: ID: #%#06x Dir: %#010x\n", + iVerSub, paVerSubEntries[iVerSub].Id, paVerSubEntries[iVerSub].OffsetToDirectory)); + } + else + { + if (!paVerSubEntries[iVerSub].DataIsDirectory) + SUP_RSRC_DPRINTF((" #%u: Name: #%#06x Data: %#010x\n", + iVerSub, paVerSubEntries[iVerSub].NameOffset, paVerSubEntries[iVerSub].OffsetToData)); + else + SUP_RSRC_DPRINTF((" #%u: Name: #%#06x Dir: %#010x\n", + iVerSub, paVerSubEntries[iVerSub].NameOffset, paVerSubEntries[iVerSub].OffsetToDirectory)); + } + if (!paVerSubEntries[iVerSub].DataIsDirectory) + { + SUP_DPRINTF((" [Version info resource found at %#x! (ID/Name: %#x; SubID/SubName: %#x)]\n", + paVerSubEntries[iVerSub].OffsetToData, paVerEntries[iVer].Name, paVerSubEntries[iVerSub].Name)); + return supR3HardenedGetRvaFromRsrcDataEntry(pRootDir, cbBuf, paVerSubEntries[iVerSub].OffsetToData, pcbData); + } + } + } + } + + *pcbData = 0; + return UINT32_MAX; +} + + +/** + * Logs information about a file from a protection product or from Windows, + * optionally returning the file version. + * + * The purpose here is to better see which version of the product is installed + * and not needing to depend on the user supplying the correct information. + * + * @param pwszFile The NT path to the file. + * @param pwszFileVersion Where to return the file version, if found. NULL if + * not interested. + * @param cwcFileVersion The size of the file version buffer (UTF-16 units). + */ +static void supR3HardenedLogFileInfo(PCRTUTF16 pwszFile, PRTUTF16 pwszFileVersion, size_t cwcFileVersion) +{ + /* + * Make sure the file version is always set when we return. + */ + if (pwszFileVersion && cwcFileVersion) + *pwszFileVersion = '\0'; + + /* + * Open the file. + */ + HANDLE hFile = RTNT_INVALID_HANDLE_VALUE; + IO_STATUS_BLOCK Ios = RTNT_IO_STATUS_BLOCK_INITIALIZER; + UNICODE_STRING UniStrName; + UniStrName.Buffer = (WCHAR *)pwszFile; + UniStrName.Length = (USHORT)(RTUtf16Len(pwszFile) * sizeof(WCHAR)); + UniStrName.MaximumLength = UniStrName.Length + sizeof(WCHAR); + OBJECT_ATTRIBUTES ObjAttr; + InitializeObjectAttributes(&ObjAttr, &UniStrName, OBJ_CASE_INSENSITIVE, NULL /*hRootDir*/, NULL /*pSecDesc*/); + 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)) + { + SUP_DPRINTF(("%ls:\n", pwszFile)); + union + { + uint64_t u64AlignmentInsurance; + FILE_BASIC_INFORMATION BasicInfo; + FILE_STANDARD_INFORMATION StdInfo; + uint8_t abBuf[32768]; + RTUTF16 awcBuf[16384]; + IMAGE_DOS_HEADER MzHdr; + IMAGE_RESOURCE_DIRECTORY ResDir; + } u; + RTTIMESPEC TimeSpec; + char szTmp[64]; + + /* + * Print basic file information available via NtQueryInformationFile. + */ + RTNT_IO_STATUS_BLOCK_REINIT(&Ios); + rcNt = NtQueryInformationFile(hFile, &Ios, &u.BasicInfo, sizeof(u.BasicInfo), FileBasicInformation); + if (NT_SUCCESS(rcNt) && NT_SUCCESS(Ios.Status)) + { + SUP_DPRINTF((" CreationTime: %s\n", RTTimeSpecToString(RTTimeSpecSetNtTime(&TimeSpec, u.BasicInfo.CreationTime.QuadPart), szTmp, sizeof(szTmp)))); + /*SUP_DPRINTF((" LastAccessTime: %s\n", RTTimeSpecToString(RTTimeSpecSetNtTime(&TimeSpec, u.BasicInfo.LastAccessTime.QuadPart), szTmp, sizeof(szTmp))));*/ + SUP_DPRINTF((" LastWriteTime: %s\n", RTTimeSpecToString(RTTimeSpecSetNtTime(&TimeSpec, u.BasicInfo.LastWriteTime.QuadPart), szTmp, sizeof(szTmp)))); + SUP_DPRINTF((" ChangeTime: %s\n", RTTimeSpecToString(RTTimeSpecSetNtTime(&TimeSpec, u.BasicInfo.ChangeTime.QuadPart), szTmp, sizeof(szTmp)))); + SUP_DPRINTF((" FileAttributes: %#x\n", u.BasicInfo.FileAttributes)); + } + else + SUP_DPRINTF((" FileBasicInformation -> %#x %#x\n", rcNt, Ios.Status)); + + RTNT_IO_STATUS_BLOCK_REINIT(&Ios); + rcNt = NtQueryInformationFile(hFile, &Ios, &u.StdInfo, sizeof(u.StdInfo), FileStandardInformation); + if (NT_SUCCESS(rcNt) && NT_SUCCESS(Ios.Status)) + SUP_DPRINTF((" Size: %#llx\n", u.StdInfo.EndOfFile.QuadPart)); + else + SUP_DPRINTF((" FileStandardInformation -> %#x %#x\n", rcNt, Ios.Status)); + + /* + * Read the image header and extract the timestamp and other useful info. + */ + RT_ZERO(u); + RTNT_IO_STATUS_BLOCK_REINIT(&Ios); + LARGE_INTEGER offRead; + offRead.QuadPart = 0; + rcNt = NtReadFile(hFile, NULL /*hEvent*/, NULL /*ApcRoutine*/, NULL /*ApcContext*/, &Ios, + &u, (ULONG)sizeof(u), &offRead, NULL); + if (NT_SUCCESS(rcNt) && NT_SUCCESS(Ios.Status)) + { + uint32_t offNtHdrs = 0; + if (u.MzHdr.e_magic == IMAGE_DOS_SIGNATURE) + offNtHdrs = u.MzHdr.e_lfanew; + if (offNtHdrs < sizeof(u) - sizeof(IMAGE_NT_HEADERS)) + { + PIMAGE_NT_HEADERS64 pNtHdrs64 = (PIMAGE_NT_HEADERS64)&u.abBuf[offNtHdrs]; + PIMAGE_NT_HEADERS32 pNtHdrs32 = (PIMAGE_NT_HEADERS32)&u.abBuf[offNtHdrs]; + if (pNtHdrs64->Signature == IMAGE_NT_SIGNATURE) + { + SUP_DPRINTF((" NT Headers: %#x\n", offNtHdrs)); + SUP_DPRINTF((" Timestamp: %#x\n", pNtHdrs64->FileHeader.TimeDateStamp)); + SUP_DPRINTF((" Machine: %#x%s\n", pNtHdrs64->FileHeader.Machine, + pNtHdrs64->FileHeader.Machine == IMAGE_FILE_MACHINE_I386 ? " - i386" + : pNtHdrs64->FileHeader.Machine == IMAGE_FILE_MACHINE_AMD64 ? " - amd64" : "")); + SUP_DPRINTF((" Timestamp: %#x\n", pNtHdrs64->FileHeader.TimeDateStamp)); + SUP_DPRINTF((" Image Version: %u.%u\n", + pNtHdrs64->OptionalHeader.MajorImageVersion, pNtHdrs64->OptionalHeader.MinorImageVersion)); + SUP_DPRINTF((" SizeOfImage: %#x (%u)\n", pNtHdrs64->OptionalHeader.SizeOfImage, pNtHdrs64->OptionalHeader.SizeOfImage)); + + /* + * Very crude way to extract info from the file version resource. + */ + PIMAGE_SECTION_HEADER paSectHdrs = (PIMAGE_SECTION_HEADER)( (uintptr_t)&pNtHdrs64->OptionalHeader + + pNtHdrs64->FileHeader.SizeOfOptionalHeader); + IMAGE_DATA_DIRECTORY RsrcDir = { 0, 0 }; + if ( pNtHdrs64->FileHeader.SizeOfOptionalHeader == sizeof(IMAGE_OPTIONAL_HEADER64) + && pNtHdrs64->OptionalHeader.NumberOfRvaAndSizes > IMAGE_DIRECTORY_ENTRY_RESOURCE) + RsrcDir = pNtHdrs64->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_RESOURCE]; + else if ( pNtHdrs64->FileHeader.SizeOfOptionalHeader == sizeof(IMAGE_OPTIONAL_HEADER32) + && pNtHdrs32->OptionalHeader.NumberOfRvaAndSizes > IMAGE_DIRECTORY_ENTRY_RESOURCE) + RsrcDir = pNtHdrs32->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_RESOURCE]; + SUP_DPRINTF((" Resource Dir: %#x LB %#x\n", RsrcDir.VirtualAddress, RsrcDir.Size)); + if ( RsrcDir.VirtualAddress > offNtHdrs + && RsrcDir.Size > 0 + && (uintptr_t)&u + sizeof(u) - (uintptr_t)paSectHdrs + >= pNtHdrs64->FileHeader.NumberOfSections * sizeof(IMAGE_SECTION_HEADER) ) + { + uint32_t uRvaRsrcSect = 0; + uint32_t cbRsrcSect = 0; + uint32_t offRsrcSect = 0; + offRead.QuadPart = 0; + for (uint32_t i = 0; i < pNtHdrs64->FileHeader.NumberOfSections; i++) + { + uRvaRsrcSect = paSectHdrs[i].VirtualAddress; + cbRsrcSect = paSectHdrs[i].Misc.VirtualSize; + offRsrcSect = paSectHdrs[i].PointerToRawData; + if ( RsrcDir.VirtualAddress - uRvaRsrcSect < cbRsrcSect + && offRsrcSect > offNtHdrs) + { + offRead.QuadPart = offRsrcSect + (RsrcDir.VirtualAddress - uRvaRsrcSect); + break; + } + } + if (offRead.QuadPart > 0) + { + RTNT_IO_STATUS_BLOCK_REINIT(&Ios); + RT_ZERO(u); + rcNt = NtReadFile(hFile, NULL /*hEvent*/, NULL /*ApcRoutine*/, NULL /*ApcContext*/, &Ios, + &u, (ULONG)sizeof(u), &offRead, NULL); + PCRTUTF16 pwcVersionData = &u.awcBuf[0]; + size_t cbVersionData = sizeof(u); + + if (NT_SUCCESS(rcNt) && NT_SUCCESS(Ios.Status)) + { + /* Make it less crude by try find the version resource data. */ + uint32_t cbVersion; + uint32_t uRvaVersion = supR3HardenedFindVersionRsrcRva(&u.ResDir, sizeof(u), &cbVersion); + NOREF(uRvaVersion); + if ( uRvaVersion != UINT32_MAX + && cbVersion < cbRsrcSect + && uRvaVersion - uRvaRsrcSect <= cbRsrcSect - cbVersion) + { + uint32_t const offVersion = uRvaVersion - uRvaRsrcSect; + if ( offVersion < sizeof(u) + && offVersion + cbVersion <= sizeof(u)) + { + pwcVersionData = (PCRTUTF16)&u.abBuf[offVersion]; + cbVersionData = cbVersion; + } + else + { + offRead.QuadPart = offVersion + offRsrcSect; + RT_ZERO(u); + rcNt = NtReadFile(hFile, NULL /*hEvent*/, NULL /*ApcRoutine*/, NULL /*ApcContext*/, &Ios, + &u, (ULONG)sizeof(u), &offRead, NULL); + pwcVersionData = &u.awcBuf[0]; + cbVersionData = RT_MIN(cbVersion, sizeof(u)); + } + } + } + + if (NT_SUCCESS(rcNt) && NT_SUCCESS(Ios.Status)) + { + static const struct { PCRTUTF16 pwsz; size_t cb; bool fRet; } s_abFields[] = + { +#define MY_WIDE_STR_TUPLE(a_sz, a_fRet) { L ## a_sz, sizeof(L ## a_sz) - sizeof(RTUTF16), a_fRet } + MY_WIDE_STR_TUPLE("ProductName", false), + MY_WIDE_STR_TUPLE("ProductVersion", false), + MY_WIDE_STR_TUPLE("FileVersion", true), + MY_WIDE_STR_TUPLE("SpecialBuild", false), + MY_WIDE_STR_TUPLE("PrivateBuild", false), + MY_WIDE_STR_TUPLE("FileDescription", false), +#undef MY_WIDE_STR_TUPLE + }; + for (uint32_t i = 0; i < RT_ELEMENTS(s_abFields); i++) + { + if (cbVersionData <= s_abFields[i].cb + 10) + continue; + size_t cwcLeft = (cbVersionData - s_abFields[i].cb - 10) / sizeof(RTUTF16); + PCRTUTF16 pwc = pwcVersionData; + RTUTF16 const wcFirst = *s_abFields[i].pwsz; + while (cwcLeft-- > 0) + { + if ( pwc[0] == 1 /* wType == text */ + && pwc[1] == wcFirst) + { + if (memcmp(pwc + 1, s_abFields[i].pwsz, s_abFields[i].cb + sizeof(RTUTF16)) == 0) + { + size_t cwcField = s_abFields[i].cb / sizeof(RTUTF16); + pwc += cwcField + 2; + cwcLeft -= cwcField + 2; + for (uint32_t iPadding = 0; iPadding < 3; iPadding++, pwc++, cwcLeft--) + if (*pwc) + break; + int rc = RTUtf16ValidateEncodingEx(pwc, cwcLeft, + RTSTR_VALIDATE_ENCODING_ZERO_TERMINATED); + if (RT_SUCCESS(rc)) + { + SUP_DPRINTF((" %ls:%*s %ls", + s_abFields[i].pwsz, cwcField < 15 ? 15 - cwcField : 0, "", pwc)); + if ( s_abFields[i].fRet + && pwszFileVersion + && cwcFileVersion > 1) + RTUtf16Copy(pwszFileVersion, cwcFileVersion, pwc); + } + else + SUP_DPRINTF((" %ls:%*s rc=%Rrc", + s_abFields[i].pwsz, cwcField < 15 ? 15 - cwcField : 0, "", rc)); + + break; + } + } + pwc++; + } + } + } + else + SUP_DPRINTF((" NtReadFile @%#llx -> %#x %#x\n", offRead.QuadPart, rcNt, Ios.Status)); + } + else + SUP_DPRINTF((" Resource section not found.\n")); + } + } + else + SUP_DPRINTF((" Nt Headers @%#x: Invalid signature\n", offNtHdrs)); + } + else + SUP_DPRINTF((" Nt Headers @%#x: out side buffer\n", offNtHdrs)); + } + else + SUP_DPRINTF((" NtReadFile @0 -> %#x %#x\n", rcNt, Ios.Status)); + NtClose(hFile); + } +} + + +/** + * Scans the Driver directory for drivers which may invade our processes. + * + * @returns Mask of SUPHARDNT_ADVERSARY_XXX flags. + * + * @remarks The enumeration of \\Driver normally requires administrator + * privileges. So, the detection we're doing here isn't always gonna + * work just based on that. + * + * @todo Find drivers in \\FileSystems as well, then we could detect VrNsdDrv + * from ViRobot APT Shield 2.0. + */ +static uint32_t supR3HardenedWinFindAdversaries(void) +{ + static const struct + { + uint32_t fAdversary; + const char *pszDriver; + } s_aDrivers[] = + { + { SUPHARDNT_ADVERSARY_SYMANTEC_SYSPLANT, "SysPlant" }, + + { SUPHARDNT_ADVERSARY_SYMANTEC_N360, "SRTSPX" }, + { SUPHARDNT_ADVERSARY_SYMANTEC_N360, "SymDS" }, + { SUPHARDNT_ADVERSARY_SYMANTEC_N360, "SymEvent" }, + { SUPHARDNT_ADVERSARY_SYMANTEC_N360, "SymIRON" }, + { SUPHARDNT_ADVERSARY_SYMANTEC_N360, "SymNetS" }, + + { SUPHARDNT_ADVERSARY_AVAST, "aswHwid" }, + { SUPHARDNT_ADVERSARY_AVAST, "aswMonFlt" }, + { SUPHARDNT_ADVERSARY_AVAST, "aswRdr2" }, + { SUPHARDNT_ADVERSARY_AVAST, "aswRvrt" }, + { SUPHARDNT_ADVERSARY_AVAST, "aswSnx" }, + { SUPHARDNT_ADVERSARY_AVAST, "aswsp" }, + { SUPHARDNT_ADVERSARY_AVAST, "aswStm" }, + { SUPHARDNT_ADVERSARY_AVAST, "aswVmm" }, + + { SUPHARDNT_ADVERSARY_TRENDMICRO, "tmcomm" }, + { SUPHARDNT_ADVERSARY_TRENDMICRO, "tmactmon" }, + { SUPHARDNT_ADVERSARY_TRENDMICRO, "tmevtmgr" }, + { SUPHARDNT_ADVERSARY_TRENDMICRO, "tmtdi" }, + { SUPHARDNT_ADVERSARY_TRENDMICRO, "tmebc64" }, /* Titanium internet security, not officescan. */ + { SUPHARDNT_ADVERSARY_TRENDMICRO, "tmeevw" }, /* Titanium internet security, not officescan. */ + { SUPHARDNT_ADVERSARY_TRENDMICRO, "tmciesc" }, /* Titanium internet security, not officescan. */ + + { SUPHARDNT_ADVERSARY_MCAFEE, "cfwids" }, + { SUPHARDNT_ADVERSARY_MCAFEE, "McPvDrv" }, + { SUPHARDNT_ADVERSARY_MCAFEE, "mfeapfk" }, + { SUPHARDNT_ADVERSARY_MCAFEE, "mfeavfk" }, + { SUPHARDNT_ADVERSARY_MCAFEE, "mfefirek" }, + { SUPHARDNT_ADVERSARY_MCAFEE, "mfehidk" }, + { SUPHARDNT_ADVERSARY_MCAFEE, "mfencbdc" }, + { SUPHARDNT_ADVERSARY_MCAFEE, "mfewfpk" }, + + { SUPHARDNT_ADVERSARY_KASPERSKY, "kl1" }, + { SUPHARDNT_ADVERSARY_KASPERSKY, "klflt" }, + { SUPHARDNT_ADVERSARY_KASPERSKY, "klif" }, + { SUPHARDNT_ADVERSARY_KASPERSKY, "KLIM6" }, + { SUPHARDNT_ADVERSARY_KASPERSKY, "klkbdflt" }, + { SUPHARDNT_ADVERSARY_KASPERSKY, "klmouflt" }, + { SUPHARDNT_ADVERSARY_KASPERSKY, "kltdi" }, + { SUPHARDNT_ADVERSARY_KASPERSKY, "kneps" }, + + { SUPHARDNT_ADVERSARY_MBAM, "MBAMWebAccessControl" }, + { SUPHARDNT_ADVERSARY_MBAM, "mbam" }, + { SUPHARDNT_ADVERSARY_MBAM, "mbamchameleon" }, + { SUPHARDNT_ADVERSARY_MBAM, "mwav" }, + { SUPHARDNT_ADVERSARY_MBAM, "mbamswissarmy" }, + + { SUPHARDNT_ADVERSARY_AVG, "avgfwfd" }, + { SUPHARDNT_ADVERSARY_AVG, "avgtdia" }, + + { SUPHARDNT_ADVERSARY_PANDA, "PSINAflt" }, + { SUPHARDNT_ADVERSARY_PANDA, "PSINFile" }, + { SUPHARDNT_ADVERSARY_PANDA, "PSINKNC" }, + { SUPHARDNT_ADVERSARY_PANDA, "PSINProc" }, + { SUPHARDNT_ADVERSARY_PANDA, "PSINProt" }, + { SUPHARDNT_ADVERSARY_PANDA, "PSINReg" }, + { SUPHARDNT_ADVERSARY_PANDA, "PSKMAD" }, + { SUPHARDNT_ADVERSARY_PANDA, "NNSAlpc" }, + { SUPHARDNT_ADVERSARY_PANDA, "NNSHttp" }, + { SUPHARDNT_ADVERSARY_PANDA, "NNShttps" }, + { SUPHARDNT_ADVERSARY_PANDA, "NNSIds" }, + { SUPHARDNT_ADVERSARY_PANDA, "NNSNAHSL" }, + { SUPHARDNT_ADVERSARY_PANDA, "NNSpicc" }, + { SUPHARDNT_ADVERSARY_PANDA, "NNSPihsw" }, + { SUPHARDNT_ADVERSARY_PANDA, "NNSPop3" }, + { SUPHARDNT_ADVERSARY_PANDA, "NNSProt" }, + { SUPHARDNT_ADVERSARY_PANDA, "NNSPrv" }, + { SUPHARDNT_ADVERSARY_PANDA, "NNSSmtp" }, + { SUPHARDNT_ADVERSARY_PANDA, "NNSStrm" }, + { SUPHARDNT_ADVERSARY_PANDA, "NNStlsc" }, + + { SUPHARDNT_ADVERSARY_MSE, "NisDrv" }, + + /*{ SUPHARDNT_ADVERSARY_COMODO, "cmdguard" }, file system */ + { SUPHARDNT_ADVERSARY_COMODO, "inspect" }, + { SUPHARDNT_ADVERSARY_COMODO, "cmdHlp" }, + + { SUPHARDNT_ADVERSARY_DIGITAL_GUARDIAN_OLD, "dgmaster" }, + + { SUPHARDNT_ADVERSARY_CYLANCE, "cyprotectdrv" }, /* Not verified. */ + + { SUPHARDNT_ADVERSARY_BEYONDTRUST, "privman" }, /* Not verified. */ + { SUPHARDNT_ADVERSARY_BEYONDTRUST, "privmanfi" }, /* Not verified. */ + + { SUPHARDNT_ADVERSARY_AVECTO, "PGDriver" }, + + { SUPHARDNT_ADVERSARY_SOPHOS, "SophosED" }, /* Not verified. */ + + { SUPHARDNT_ADVERSARY_HORIZON_VIEW_AGENT, "vmwicpdr" }, + }; + + static const struct + { + uint32_t fAdversary; + PCRTUTF16 pwszFile; + } s_aFiles[] = + { + { SUPHARDNT_ADVERSARY_SYMANTEC_SYSPLANT, L"\\SystemRoot\\System32\\drivers\\SysPlant.sys" }, + { SUPHARDNT_ADVERSARY_SYMANTEC_SYSPLANT, L"\\SystemRoot\\System32\\sysfer.dll" }, + { SUPHARDNT_ADVERSARY_SYMANTEC_SYSPLANT, L"\\SystemRoot\\System32\\sysferThunk.dll" }, + + { SUPHARDNT_ADVERSARY_SYMANTEC_N360, L"\\SystemRoot\\System32\\drivers\\N360x64\\1505000.013\\ccsetx64.sys" }, + { SUPHARDNT_ADVERSARY_SYMANTEC_N360, L"\\SystemRoot\\System32\\drivers\\N360x64\\1505000.013\\ironx64.sys" }, + { SUPHARDNT_ADVERSARY_SYMANTEC_N360, L"\\SystemRoot\\System32\\drivers\\N360x64\\1505000.013\\srtsp64.sys" }, + { SUPHARDNT_ADVERSARY_SYMANTEC_N360, L"\\SystemRoot\\System32\\drivers\\N360x64\\1505000.013\\srtspx64.sys" }, + { SUPHARDNT_ADVERSARY_SYMANTEC_N360, L"\\SystemRoot\\System32\\drivers\\N360x64\\1505000.013\\symds64.sys" }, + { SUPHARDNT_ADVERSARY_SYMANTEC_N360, L"\\SystemRoot\\System32\\drivers\\N360x64\\1505000.013\\symefa64.sys" }, + { SUPHARDNT_ADVERSARY_SYMANTEC_N360, L"\\SystemRoot\\System32\\drivers\\N360x64\\1505000.013\\symelam.sys" }, + { SUPHARDNT_ADVERSARY_SYMANTEC_N360, L"\\SystemRoot\\System32\\drivers\\N360x64\\1505000.013\\symnets.sys" }, + { SUPHARDNT_ADVERSARY_SYMANTEC_N360, L"\\SystemRoot\\System32\\drivers\\symevent64x86.sys" }, + + { SUPHARDNT_ADVERSARY_AVAST, L"\\SystemRoot\\System32\\drivers\\aswHwid.sys" }, + { SUPHARDNT_ADVERSARY_AVAST, L"\\SystemRoot\\System32\\drivers\\aswMonFlt.sys" }, + { SUPHARDNT_ADVERSARY_AVAST, L"\\SystemRoot\\System32\\drivers\\aswRdr2.sys" }, + { SUPHARDNT_ADVERSARY_AVAST, L"\\SystemRoot\\System32\\drivers\\aswRvrt.sys" }, + { SUPHARDNT_ADVERSARY_AVAST, L"\\SystemRoot\\System32\\drivers\\aswSnx.sys" }, + { SUPHARDNT_ADVERSARY_AVAST, L"\\SystemRoot\\System32\\drivers\\aswsp.sys" }, + { SUPHARDNT_ADVERSARY_AVAST, L"\\SystemRoot\\System32\\drivers\\aswStm.sys" }, + { SUPHARDNT_ADVERSARY_AVAST, L"\\SystemRoot\\System32\\drivers\\aswVmm.sys" }, + + { SUPHARDNT_ADVERSARY_TRENDMICRO, L"\\SystemRoot\\System32\\drivers\\tmcomm.sys" }, + { SUPHARDNT_ADVERSARY_TRENDMICRO, L"\\SystemRoot\\System32\\drivers\\tmactmon.sys" }, + { SUPHARDNT_ADVERSARY_TRENDMICRO, L"\\SystemRoot\\System32\\drivers\\tmevtmgr.sys" }, + { SUPHARDNT_ADVERSARY_TRENDMICRO, L"\\SystemRoot\\System32\\drivers\\tmtdi.sys" }, + { SUPHARDNT_ADVERSARY_TRENDMICRO, L"\\SystemRoot\\System32\\drivers\\tmebc64.sys" }, + { SUPHARDNT_ADVERSARY_TRENDMICRO, L"\\SystemRoot\\System32\\drivers\\tmeevw.sys" }, + { SUPHARDNT_ADVERSARY_TRENDMICRO, L"\\SystemRoot\\System32\\drivers\\tmciesc.sys" }, + { SUPHARDNT_ADVERSARY_TRENDMICRO_SAKFILE, L"\\SystemRoot\\System32\\drivers\\sakfile.sys" }, /* Data Loss Prevention, not officescan. */ + { SUPHARDNT_ADVERSARY_TRENDMICRO, L"\\SystemRoot\\System32\\drivers\\sakcd.sys" }, /* Data Loss Prevention, not officescan. */ + + + { SUPHARDNT_ADVERSARY_MCAFEE, L"\\SystemRoot\\System32\\drivers\\cfwids.sys" }, + { SUPHARDNT_ADVERSARY_MCAFEE, L"\\SystemRoot\\System32\\drivers\\McPvDrv.sys" }, + { SUPHARDNT_ADVERSARY_MCAFEE, L"\\SystemRoot\\System32\\drivers\\mfeapfk.sys" }, + { SUPHARDNT_ADVERSARY_MCAFEE, L"\\SystemRoot\\System32\\drivers\\mfeavfk.sys" }, + { SUPHARDNT_ADVERSARY_MCAFEE, L"\\SystemRoot\\System32\\drivers\\mfefirek.sys" }, + { SUPHARDNT_ADVERSARY_MCAFEE, L"\\SystemRoot\\System32\\drivers\\mfehidk.sys" }, + { SUPHARDNT_ADVERSARY_MCAFEE, L"\\SystemRoot\\System32\\drivers\\mfencbdc.sys" }, + { SUPHARDNT_ADVERSARY_MCAFEE, L"\\SystemRoot\\System32\\drivers\\mfewfpk.sys" }, + + { SUPHARDNT_ADVERSARY_KASPERSKY, L"\\SystemRoot\\System32\\drivers\\kl1.sys" }, + { SUPHARDNT_ADVERSARY_KASPERSKY, L"\\SystemRoot\\System32\\drivers\\klflt.sys" }, + { SUPHARDNT_ADVERSARY_KASPERSKY, L"\\SystemRoot\\System32\\drivers\\klif.sys" }, + { SUPHARDNT_ADVERSARY_KASPERSKY, L"\\SystemRoot\\System32\\drivers\\klim6.sys" }, + { SUPHARDNT_ADVERSARY_KASPERSKY, L"\\SystemRoot\\System32\\drivers\\klkbdflt.sys" }, + { SUPHARDNT_ADVERSARY_KASPERSKY, L"\\SystemRoot\\System32\\drivers\\klmouflt.sys" }, + { SUPHARDNT_ADVERSARY_KASPERSKY, L"\\SystemRoot\\System32\\drivers\\kltdi.sys" }, + { SUPHARDNT_ADVERSARY_KASPERSKY, L"\\SystemRoot\\System32\\drivers\\kneps.sys" }, + { SUPHARDNT_ADVERSARY_KASPERSKY, L"\\SystemRoot\\System32\\klfphc.dll" }, + + { SUPHARDNT_ADVERSARY_MBAM, L"\\SystemRoot\\System32\\drivers\\MBAMSwissArmy.sys" }, + { SUPHARDNT_ADVERSARY_MBAM, L"\\SystemRoot\\System32\\drivers\\mwac.sys" }, + { SUPHARDNT_ADVERSARY_MBAM, L"\\SystemRoot\\System32\\drivers\\mbamchameleon.sys" }, + { SUPHARDNT_ADVERSARY_MBAM, L"\\SystemRoot\\System32\\drivers\\mbam.sys" }, + + { SUPHARDNT_ADVERSARY_AVG, L"\\SystemRoot\\System32\\drivers\\avgrkx64.sys" }, + { SUPHARDNT_ADVERSARY_AVG, L"\\SystemRoot\\System32\\drivers\\avgmfx64.sys" }, + { SUPHARDNT_ADVERSARY_AVG, L"\\SystemRoot\\System32\\drivers\\avgidsdrivera.sys" }, + { SUPHARDNT_ADVERSARY_AVG, L"\\SystemRoot\\System32\\drivers\\avgidsha.sys" }, + { SUPHARDNT_ADVERSARY_AVG, L"\\SystemRoot\\System32\\drivers\\avgtdia.sys" }, + { SUPHARDNT_ADVERSARY_AVG, L"\\SystemRoot\\System32\\drivers\\avgloga.sys" }, + { SUPHARDNT_ADVERSARY_AVG, L"\\SystemRoot\\System32\\drivers\\avgldx64.sys" }, + { SUPHARDNT_ADVERSARY_AVG, L"\\SystemRoot\\System32\\drivers\\avgdiska.sys" }, + + { SUPHARDNT_ADVERSARY_PANDA, L"\\SystemRoot\\System32\\drivers\\PSINAflt.sys" }, + { SUPHARDNT_ADVERSARY_PANDA, L"\\SystemRoot\\System32\\drivers\\PSINFile.sys" }, + { SUPHARDNT_ADVERSARY_PANDA, L"\\SystemRoot\\System32\\drivers\\PSINKNC.sys" }, + { SUPHARDNT_ADVERSARY_PANDA, L"\\SystemRoot\\System32\\drivers\\PSINProc.sys" }, + { SUPHARDNT_ADVERSARY_PANDA, L"\\SystemRoot\\System32\\drivers\\PSINProt.sys" }, + { SUPHARDNT_ADVERSARY_PANDA, L"\\SystemRoot\\System32\\drivers\\PSINReg.sys" }, + { SUPHARDNT_ADVERSARY_PANDA, L"\\SystemRoot\\System32\\drivers\\PSKMAD.sys" }, + { SUPHARDNT_ADVERSARY_PANDA, L"\\SystemRoot\\System32\\drivers\\NNSAlpc.sys" }, + { SUPHARDNT_ADVERSARY_PANDA, L"\\SystemRoot\\System32\\drivers\\NNSHttp.sys" }, + { SUPHARDNT_ADVERSARY_PANDA, L"\\SystemRoot\\System32\\drivers\\NNShttps.sys" }, + { SUPHARDNT_ADVERSARY_PANDA, L"\\SystemRoot\\System32\\drivers\\NNSIds.sys" }, + { SUPHARDNT_ADVERSARY_PANDA, L"\\SystemRoot\\System32\\drivers\\NNSNAHSL.sys" }, + { SUPHARDNT_ADVERSARY_PANDA, L"\\SystemRoot\\System32\\drivers\\NNSpicc.sys" }, + { SUPHARDNT_ADVERSARY_PANDA, L"\\SystemRoot\\System32\\drivers\\NNSPihsw.sys" }, + { SUPHARDNT_ADVERSARY_PANDA, L"\\SystemRoot\\System32\\drivers\\NNSPop3.sys" }, + { SUPHARDNT_ADVERSARY_PANDA, L"\\SystemRoot\\System32\\drivers\\NNSProt.sys" }, + { SUPHARDNT_ADVERSARY_PANDA, L"\\SystemRoot\\System32\\drivers\\NNSPrv.sys" }, + { SUPHARDNT_ADVERSARY_PANDA, L"\\SystemRoot\\System32\\drivers\\NNSSmtp.sys" }, + { SUPHARDNT_ADVERSARY_PANDA, L"\\SystemRoot\\System32\\drivers\\NNSStrm.sys" }, + { SUPHARDNT_ADVERSARY_PANDA, L"\\SystemRoot\\System32\\drivers\\NNStlsc.sys" }, + + { SUPHARDNT_ADVERSARY_MSE, L"\\SystemRoot\\System32\\drivers\\MpFilter.sys" }, + { SUPHARDNT_ADVERSARY_MSE, L"\\SystemRoot\\System32\\drivers\\NisDrvWFP.sys" }, + + { SUPHARDNT_ADVERSARY_COMODO, L"\\SystemRoot\\System32\\drivers\\cmdguard.sys" }, + { SUPHARDNT_ADVERSARY_COMODO, L"\\SystemRoot\\System32\\drivers\\cmderd.sys" }, + { SUPHARDNT_ADVERSARY_COMODO, L"\\SystemRoot\\System32\\drivers\\inspect.sys" }, + { SUPHARDNT_ADVERSARY_COMODO, L"\\SystemRoot\\System32\\drivers\\cmdhlp.sys" }, + { SUPHARDNT_ADVERSARY_COMODO, L"\\SystemRoot\\System32\\drivers\\cfrmd.sys" }, + { SUPHARDNT_ADVERSARY_COMODO, L"\\SystemRoot\\System32\\drivers\\hmd.sys" }, + { SUPHARDNT_ADVERSARY_COMODO, L"\\SystemRoot\\System32\\guard64.dll" }, + { SUPHARDNT_ADVERSARY_COMODO, L"\\SystemRoot\\System32\\cmdvrt64.dll" }, + { SUPHARDNT_ADVERSARY_COMODO, L"\\SystemRoot\\System32\\cmdkbd64.dll" }, + { SUPHARDNT_ADVERSARY_COMODO, L"\\SystemRoot\\System32\\cmdcsr.dll" }, + + { SUPHARDNT_ADVERSARY_ZONE_ALARM, L"\\SystemRoot\\System32\\drivers\\vsdatant.sys" }, + { SUPHARDNT_ADVERSARY_ZONE_ALARM, L"\\SystemRoot\\System32\\AntiTheftCredentialProvider.dll" }, + + { SUPHARDNT_ADVERSARY_DIGITAL_GUARDIAN_OLD, L"\\SystemRoot\\System32\\drivers\\dgmaster.sys" }, + + { SUPHARDNT_ADVERSARY_CYLANCE, L"\\SystemRoot\\System32\\drivers\\cyprotectdrv32.sys" }, + { SUPHARDNT_ADVERSARY_CYLANCE, L"\\SystemRoot\\System32\\drivers\\cyprotectdrv64.sys" }, + + { SUPHARDNT_ADVERSARY_BEYONDTRUST, L"\\SystemRoot\\System32\\drivers\\privman.sys" }, + { SUPHARDNT_ADVERSARY_BEYONDTRUST, L"\\SystemRoot\\System32\\drivers\\privmanfi.sys" }, + { SUPHARDNT_ADVERSARY_BEYONDTRUST, L"\\SystemRoot\\System32\\privman64.dll" }, + { SUPHARDNT_ADVERSARY_BEYONDTRUST, L"\\SystemRoot\\System32\\privman32.dll" }, + + { SUPHARDNT_ADVERSARY_AVECTO, L"\\SystemRoot\\System32\\drivers\\PGDriver.sys" }, + + { SUPHARDNT_ADVERSARY_SOPHOS, L"\\SystemRoot\\System32\\drivers\\SophosED.sys" }, // not verified + + { SUPHARDNT_ADVERSARY_HORIZON_VIEW_AGENT, L"\\SystemRoot\\System32\\drivers\\vmwicpdr.sys" }, + { SUPHARDNT_ADVERSARY_HORIZON_VIEW_AGENT, L"\\SystemRoot\\System32\\drivers\\ftsjail.sys" }, + }; + + uint32_t fFound = 0; + + /* + * Open the driver object directory. + */ + UNICODE_STRING NtDirName = RTNT_CONSTANT_UNISTR(L"\\Driver"); + + OBJECT_ATTRIBUTES ObjAttr; + InitializeObjectAttributes(&ObjAttr, &NtDirName, OBJ_CASE_INSENSITIVE, NULL /*hRootDir*/, NULL /*pSecDesc*/); + + HANDLE hDir; + NTSTATUS rcNt = NtOpenDirectoryObject(&hDir, DIRECTORY_QUERY | FILE_LIST_DIRECTORY, &ObjAttr); +#ifdef VBOX_STRICT + if (rcNt != STATUS_ACCESS_DENIED) /* non-admin */ + SUPR3HARDENED_ASSERT_NT_SUCCESS(rcNt); +#endif + if (NT_SUCCESS(rcNt)) + { + /* + * Enumerate it, looking for the driver. + */ + ULONG uObjDirCtx = 0; + for (;;) + { + uint32_t abBuffer[_64K + _1K]; + ULONG cbActual; + rcNt = NtQueryDirectoryObject(hDir, + abBuffer, + sizeof(abBuffer) - 4, /* minus four for string terminator space. */ + FALSE /*ReturnSingleEntry */, + FALSE /*RestartScan*/, + &uObjDirCtx, + &cbActual); + if (!NT_SUCCESS(rcNt) || cbActual < sizeof(OBJECT_DIRECTORY_INFORMATION)) + break; + + POBJECT_DIRECTORY_INFORMATION pObjDir = (POBJECT_DIRECTORY_INFORMATION)abBuffer; + while (pObjDir->Name.Length != 0) + { + WCHAR wcSaved = pObjDir->Name.Buffer[pObjDir->Name.Length / sizeof(WCHAR)]; + pObjDir->Name.Buffer[pObjDir->Name.Length / sizeof(WCHAR)] = '\0'; + + for (uint32_t i = 0; i < RT_ELEMENTS(s_aDrivers); i++) + if (RTUtf16ICmpAscii(pObjDir->Name.Buffer, s_aDrivers[i].pszDriver) == 0) + { + fFound |= s_aDrivers[i].fAdversary; + SUP_DPRINTF(("Found driver %s (%#x)\n", s_aDrivers[i].pszDriver, s_aDrivers[i].fAdversary)); + break; + } + + pObjDir->Name.Buffer[pObjDir->Name.Length / sizeof(WCHAR)] = wcSaved; + + /* Next directory entry. */ + pObjDir++; + } + } + + NtClose(hDir); + } + else + SUP_DPRINTF(("NtOpenDirectoryObject failed on \\Driver: %#x\n", rcNt)); + + /* + * Look for files. + */ + for (uint32_t i = 0; i < RT_ELEMENTS(s_aFiles); i++) + { + HANDLE hFile = RTNT_INVALID_HANDLE_VALUE; + IO_STATUS_BLOCK Ios = RTNT_IO_STATUS_BLOCK_INITIALIZER; + UNICODE_STRING UniStrName; + UniStrName.Buffer = (WCHAR *)s_aFiles[i].pwszFile; + UniStrName.Length = (USHORT)(RTUtf16Len(s_aFiles[i].pwszFile) * sizeof(WCHAR)); + UniStrName.MaximumLength = UniStrName.Length + sizeof(WCHAR); + InitializeObjectAttributes(&ObjAttr, &UniStrName, OBJ_CASE_INSENSITIVE, NULL /*hRootDir*/, NULL /*pSecDesc*/); + 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) && NT_SUCCESS(Ios.Status)) + { + fFound |= s_aFiles[i].fAdversary; + NtClose(hFile); + } + } + + /* + * Log details and upgrade select adversaries. + */ + SUP_DPRINTF(("supR3HardenedWinFindAdversaries: %#x\n", fFound)); + for (uint32_t i = 0; i < RT_ELEMENTS(s_aFiles); i++) + if (s_aFiles[i].fAdversary & fFound) + { + if (!(s_aFiles[i].fAdversary & SUPHARDNT_ADVERSARY_DIGITAL_GUARDIAN_OLD)) + supR3HardenedLogFileInfo(s_aFiles[i].pwszFile, NULL, 0); + else + { + /* + * See if it's a newer version of the driver which doesn't BSODs when we free + * its memory. To use RTStrVersionCompare we do a rough UTF-16 -> ASCII conversion. + */ + union + { + char szFileVersion[64]; + RTUTF16 wszFileVersion[32]; + } uBuf; + supR3HardenedLogFileInfo(s_aFiles[i].pwszFile, uBuf.wszFileVersion, RT_ELEMENTS(uBuf.wszFileVersion)); + if (uBuf.wszFileVersion[0]) + { + for (uint32_t off = 0; off < RT_ELEMENTS(uBuf.wszFileVersion); off++) + { + RTUTF16 wch = uBuf.wszFileVersion[off]; + uBuf.szFileVersion[off] = (char)wch; + if (!wch) + break; + } + uBuf.szFileVersion[RT_ELEMENTS(uBuf.wszFileVersion)] = '\0'; +#define VER_IN_RANGE(a_pszFirst, a_pszLast) \ + (RTStrVersionCompare(uBuf.szFileVersion, a_pszFirst) >= 0 && RTStrVersionCompare(uBuf.szFileVersion, a_pszLast) <= 0) + if ( VER_IN_RANGE("7.3.2.0000", "999999999.9.9.9999") + || VER_IN_RANGE("7.3.1.1000", "7.3.1.3000") + || VER_IN_RANGE("7.3.0.3000", "7.3.0.999999999") + || VER_IN_RANGE("7.2.1.3000", "7.2.999999999.999999999") ) + { + uint32_t const fOldFound = fFound; + fFound = (fOldFound & ~SUPHARDNT_ADVERSARY_DIGITAL_GUARDIAN_OLD) + | SUPHARDNT_ADVERSARY_DIGITAL_GUARDIAN_NEW; + SUP_DPRINTF(("supR3HardenedWinFindAdversaries: Found newer version: %#x -> %#x\n", fOldFound, fFound)); + } + } + } + } + + return fFound; +} + + +extern "C" int main(int argc, char **argv, char **envp); + +/** + * The executable entry point. + * + * This is normally taken care of by the C runtime library, but we don't want to + * get involved with anything as complicated like the CRT in this setup. So, we + * it everything ourselves, including parameter parsing. + */ +extern "C" void __stdcall suplibHardenedWindowsMain(void) +{ + RTEXITCODE rcExit = RTEXITCODE_FAILURE; + + g_cSuplibHardenedWindowsMainCalls++; + g_enmSupR3HardenedMainState = SUPR3HARDENEDMAINSTATE_WIN_EP_CALLED; + + /* + * Initialize the NTDLL API wrappers. This aims at bypassing patched NTDLL + * in all the processes leading up the VM process. + */ + supR3HardenedWinInitImports(); + g_enmSupR3HardenedMainState = SUPR3HARDENEDMAINSTATE_WIN_IMPORTS_RESOLVED; + + /* + * Notify the parent process that we're probably capable of reporting our + * own errors. + */ + if (g_ProcParams.hEvtParent || g_ProcParams.hEvtChild) + { + SUPR3HARDENED_ASSERT(g_fSupEarlyProcessInit); + + g_ProcParams.enmRequest = kSupR3WinChildReq_CloseEvents; + NtSetEvent(g_ProcParams.hEvtParent, NULL); + + NtClose(g_ProcParams.hEvtParent); + NtClose(g_ProcParams.hEvtChild); + g_ProcParams.hEvtParent = NULL; + g_ProcParams.hEvtChild = NULL; + } + else + SUPR3HARDENED_ASSERT(!g_fSupEarlyProcessInit); + + /* + * After having resolved imports we patch the LdrInitializeThunk code so + * that it's more difficult to invade our privacy by CreateRemoteThread. + * We'll re-enable this after opening the driver or temporarily while respawning. + */ + supR3HardenedWinDisableThreadCreation(); + + /* + * Init g_uNtVerCombined. (The code is shared with SUPR3.lib and lives in + * SUPHardenedVerfiyImage-win.cpp.) + */ + supR3HardenedWinInitVersion(false /*fEarly*/); + g_enmSupR3HardenedMainState = SUPR3HARDENEDMAINSTATE_WIN_VERSION_INITIALIZED; + + /* + * Convert the arguments to UTF-8 and open the log file if specified. + * This must be done as early as possible since the code below may fail. + */ + PUNICODE_STRING pCmdLineStr = &NtCurrentPeb()->ProcessParameters->CommandLine; + int cArgs; + char **papszArgs = suplibCommandLineToArgvWStub(pCmdLineStr->Buffer, pCmdLineStr->Length / sizeof(WCHAR), &cArgs); + + supR3HardenedOpenLog(&cArgs, papszArgs); + + /* + * Log information about important system files. + */ + supR3HardenedLogFileInfo(L"\\SystemRoot\\System32\\ntdll.dll", NULL /*pwszFileVersion*/, 0 /*cwcFileVersion*/); + supR3HardenedLogFileInfo(L"\\SystemRoot\\System32\\kernel32.dll", NULL /*pwszFileVersion*/, 0 /*cwcFileVersion*/); + supR3HardenedLogFileInfo(L"\\SystemRoot\\System32\\KernelBase.dll", NULL /*pwszFileVersion*/, 0 /*cwcFileVersion*/); + supR3HardenedLogFileInfo(L"\\SystemRoot\\System32\\apisetschema.dll", NULL /*pwszFileVersion*/, 0 /*cwcFileVersion*/); + + /* + * Scan the system for adversaries, logging information about them. + */ + g_fSupAdversaries = supR3HardenedWinFindAdversaries(); + + /* + * Get the executable name, make sure it's the long version. + */ + DWORD cwcExecName = GetModuleFileNameW(GetModuleHandleW(NULL), g_wszSupLibHardenedExePath, + RT_ELEMENTS(g_wszSupLibHardenedExePath)); + if (cwcExecName >= RT_ELEMENTS(g_wszSupLibHardenedExePath)) + supR3HardenedFatalMsg("suplibHardenedWindowsMain", kSupInitOp_Integrity, VERR_BUFFER_OVERFLOW, + "The executable path is too long."); + + RTUTF16 wszLong[RT_ELEMENTS(g_wszSupLibHardenedExePath)]; + DWORD cwcLong = GetLongPathNameW(g_wszSupLibHardenedExePath, wszLong, RT_ELEMENTS(wszLong)); + if (cwcLong > 0) + { + memcpy(g_wszSupLibHardenedExePath, wszLong, (cwcLong + 1) * sizeof(RTUTF16)); + cwcExecName = cwcLong; + } + + /* The NT version of it. */ + HANDLE hFile = CreateFileW(g_wszSupLibHardenedExePath, GENERIC_READ, FILE_SHARE_READ, NULL /*pSecurityAttributes*/, + OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL /*hTemplateFile*/); + if (hFile == NULL || hFile == INVALID_HANDLE_VALUE) + supR3HardenedFatalMsg("suplibHardenedWindowsMain", kSupInitOp_Integrity, RTErrConvertFromWin32(RtlGetLastWin32Error()), + "Error opening the executable: %u (%ls).", RtlGetLastWin32Error()); + RT_ZERO(g_SupLibHardenedExeNtPath); + ULONG cbIgn; + NTSTATUS rcNt = NtQueryObject(hFile, ObjectNameInformation, &g_SupLibHardenedExeNtPath, + sizeof(g_SupLibHardenedExeNtPath) - sizeof(WCHAR), &cbIgn); + if (!NT_SUCCESS(rcNt)) + supR3HardenedFatalMsg("suplibHardenedWindowsMain", kSupInitOp_Integrity, RTErrConvertFromNtStatus(rcNt), + "NtQueryObject -> %#x (on %ls)\n", rcNt, g_wszSupLibHardenedExePath); + NtClose(hFile); + + /* The NT executable name offset / dir path length. */ + g_offSupLibHardenedExeNtName = g_SupLibHardenedExeNtPath.UniStr.Length / sizeof(WCHAR); + while ( g_offSupLibHardenedExeNtName > 1 + && g_SupLibHardenedExeNtPath.UniStr.Buffer[g_offSupLibHardenedExeNtName - 1] != '\\' ) + g_offSupLibHardenedExeNtName--; + + /* + * Preliminary app binary path init. May change when SUPR3HardenedMain is + * called (via main below). + */ + supR3HardenedWinInitAppBin(SUPSECMAIN_FLAGS_LOC_APP_BIN); + + /* + * If we've done early init already, register the DLL load notification + * callback and reinstall the NtDll patches. + */ + if (g_fSupEarlyProcessInit) + { + supR3HardenedWinRegisterDllNotificationCallback(); + supR3HardenedWinReInstallHooks(false /*fFirstCall */); + + /* + * Flush user APCs before the g_enmSupR3HardenedMainState changes + * and disables the APC restrictions. + */ + NtTestAlert(); + } + + /* + * Call the C/C++ main function. + */ + SUP_DPRINTF(("Calling main()\n")); + rcExit = (RTEXITCODE)main(cArgs, papszArgs, NULL); + + /* + * Exit the process (never return). + */ + SUP_DPRINTF(("Terminating the normal way: rcExit=%d\n", rcExit)); + suplibHardenedExit(rcExit); +} + + +/** + * Reports an error to the parent process via the process parameter structure. + * + * @param pszWhere Where this error occured, if fatal message. NULL + * if not message. + * @param enmWhat Which init operation went wrong if fatal + * message. kSupInitOp_Invalid if not message. + * @param rc The status code to report. + * @param pszFormat The format string. + * @param va The format arguments. + */ +DECLHIDDEN(void) supR3HardenedWinReportErrorToParent(const char *pszWhere, SUPINITOP enmWhat, int rc, + const char *pszFormat, va_list va) +{ + if (pszWhere) + RTStrCopy(g_ProcParams.szWhere, sizeof(g_ProcParams.szWhere), pszWhere); + else + g_ProcParams.szWhere[0] = '\0'; + RTStrPrintfV(g_ProcParams.szErrorMsg, sizeof(g_ProcParams.szErrorMsg), pszFormat, va); + g_ProcParams.enmWhat = enmWhat; + g_ProcParams.rc = RT_SUCCESS(rc) ? VERR_INTERNAL_ERROR_2 : rc; + g_ProcParams.enmRequest = kSupR3WinChildReq_Error; + + NtClearEvent(g_ProcParams.hEvtChild); + NTSTATUS rcNt = NtSetEvent(g_ProcParams.hEvtParent, NULL); + if (NT_SUCCESS(rcNt)) + { + LARGE_INTEGER Timeout; + Timeout.QuadPart = -300000000; /* 30 second */ + /*NTSTATUS rcNt =*/ NtWaitForSingleObject(g_ProcParams.hEvtChild, FALSE /*Alertable*/, &Timeout); + } +} + + +/** + * Routine called by the supR3HardenedEarlyProcessInitThunk assembly routine + * when LdrInitializeThunk is executed during process initialization. + * + * This initializes the Stub and VM processes, hooking NTDLL APIs and opening + * the device driver before any other DLLs gets loaded into the process. This + * greately reduces and controls the trusted code base of the process compared + * to opening the driver from SUPR3HardenedMain. It also avoids issues with so + * call protection software that is in the habit of patching half of the ntdll + * and kernel32 APIs in the process, making it almost indistinguishable from + * software that is up to no good. Once we've opened vboxdrv (renamed to + * vboxsup in 7.0 and 6.1.34), the process should be locked down so tightly + * that only kernel software and csrss can mess with the process. + */ +DECLASM(uintptr_t) supR3HardenedEarlyProcessInit(void) +{ + /* + * When the first thread gets here we wait for the parent to continue with + * the process purifications. The primary thread must execute for image + * load notifications to trigger, at least in more recent windows versions. + * The old trick of starting a different thread that terminates immediately + * thus doesn't work. + * + * We are not allowed to modify any data at this point because it will be + * reset by the child process purification the parent does when we stop. To + * sabotage thread creation during purification, and to avoid unnecessary + * work for the parent, we reset g_ProcParams before signalling the parent + * here. + */ + if (g_enmSupR3HardenedMainState != SUPR3HARDENEDMAINSTATE_NOT_YET_CALLED) + { + NtTerminateThread(0, 0); + return 0x22; /* crash */ + } + + /* Retrieve the data we need. */ + uintptr_t uNtDllAddr = ASMAtomicXchgPtrT(&g_ProcParams.uNtDllAddr, 0, uintptr_t); + if (!RT_VALID_PTR(uNtDllAddr)) + { + NtTerminateThread(0, 0); + return 0x23; /* crash */ + } + + HANDLE hEvtChild = g_ProcParams.hEvtChild; + HANDLE hEvtParent = g_ProcParams.hEvtParent; + if ( hEvtChild == NULL + || hEvtChild == RTNT_INVALID_HANDLE_VALUE + || hEvtParent == NULL + || hEvtParent == RTNT_INVALID_HANDLE_VALUE) + { + NtTerminateThread(0, 0); + return 0x24; /* crash */ + } + + /* Resolve the APIs we need. */ + PFNNTWAITFORSINGLEOBJECT pfnNtWaitForSingleObject; + PFNNTSETEVENT pfnNtSetEvent; + supR3HardenedWinGetVeryEarlyImports(uNtDllAddr, &pfnNtWaitForSingleObject, &pfnNtSetEvent); + + /* Signal the parent that we're ready for purification. */ + RT_ZERO(g_ProcParams); + g_ProcParams.enmRequest = kSupR3WinChildReq_PurifyChildAndCloseHandles; + NTSTATUS rcNt = pfnNtSetEvent(hEvtParent, NULL); + if (rcNt != STATUS_SUCCESS) + return 0x33; /* crash */ + + /* Wait up to 2 mins for the parent to exorcise evil. */ + LARGE_INTEGER Timeout; + Timeout.QuadPart = -1200000000; /* 120 second */ + rcNt = pfnNtWaitForSingleObject(hEvtChild, FALSE /*Alertable (never alertable before hooking!) */, &Timeout); + if (rcNt != STATUS_SUCCESS) + return 0x34; /* crash */ + + /* + * We're good to go, work global state and restore process parameters. + * Note that we will not restore uNtDllAddr since that is our first defence + * against unwanted threads (see above). + */ + g_enmSupR3HardenedMainState = SUPR3HARDENEDMAINSTATE_WIN_EARLY_INIT_CALLED; + g_fSupEarlyProcessInit = true; + + g_ProcParams.hEvtChild = hEvtChild; + g_ProcParams.hEvtParent = hEvtParent; + g_ProcParams.enmRequest = kSupR3WinChildReq_Error; + g_ProcParams.rc = VINF_SUCCESS; + + /* + * Initialize the NTDLL imports that we consider usable before the + * process has been initialized. + */ + supR3HardenedWinInitImportsEarly(uNtDllAddr); + g_enmSupR3HardenedMainState = SUPR3HARDENEDMAINSTATE_WIN_EARLY_IMPORTS_RESOLVED; + + /* + * Init g_uNtVerCombined as well as we can at this point. + */ + supR3HardenedWinInitVersion(true /*fEarly*/); + + /* + * Convert the arguments to UTF-8 so we can open the log file if specified. + * We may have to normalize the pointer on older windows version (not w7/64 +). + * Note! This leaks memory at present. + */ + PRTL_USER_PROCESS_PARAMETERS pUserProcParams = NtCurrentPeb()->ProcessParameters; + UNICODE_STRING CmdLineStr = pUserProcParams->CommandLine; + if ( CmdLineStr.Buffer != NULL + && !(pUserProcParams->Flags & RTL_USER_PROCESS_PARAMS_FLAG_NORMALIZED) ) + CmdLineStr.Buffer = (WCHAR *)((uintptr_t)CmdLineStr.Buffer + (uintptr_t)pUserProcParams); + int cArgs; + char **papszArgs = suplibCommandLineToArgvWStub(CmdLineStr.Buffer, CmdLineStr.Length / sizeof(WCHAR), &cArgs); + supR3HardenedOpenLog(&cArgs, papszArgs); + SUP_DPRINTF(("supR3HardenedVmProcessInit: uNtDllAddr=%p g_uNtVerCombined=%#x (stack ~%p)\n", + uNtDllAddr, g_uNtVerCombined, &Timeout)); + + /* + * Set up the direct system calls so we can more easily hook NtCreateSection. + */ + RTERRINFOSTATIC ErrInfo; + supR3HardenedWinInitSyscalls(true /*fReportErrors*/, RTErrInfoInitStatic(&ErrInfo)); + + /* + * Determine the executable path and name. Will NOT determine the windows style + * executable path here as we don't need it. + */ + SIZE_T cbActual = 0; + rcNt = NtQueryVirtualMemory(NtCurrentProcess(), &g_ProcParams, MemorySectionName, &g_SupLibHardenedExeNtPath, + sizeof(g_SupLibHardenedExeNtPath) - sizeof(WCHAR), &cbActual); + if ( !NT_SUCCESS(rcNt) + || g_SupLibHardenedExeNtPath.UniStr.Length == 0 + || g_SupLibHardenedExeNtPath.UniStr.Length & 1) + supR3HardenedFatal("NtQueryVirtualMemory/MemorySectionName failed in supR3HardenedVmProcessInit: %#x\n", rcNt); + + /* The NT executable name offset / dir path length. */ + g_offSupLibHardenedExeNtName = g_SupLibHardenedExeNtPath.UniStr.Length / sizeof(WCHAR); + while ( g_offSupLibHardenedExeNtName > 1 + && g_SupLibHardenedExeNtPath.UniStr.Buffer[g_offSupLibHardenedExeNtName - 1] != '\\' ) + g_offSupLibHardenedExeNtName--; + + /* + * Preliminary app binary path init. May change when SUPR3HardenedMain is called. + */ + supR3HardenedWinInitAppBin(SUPSECMAIN_FLAGS_LOC_APP_BIN); + + /* + * Initialize the image verification stuff (hooks LdrLoadDll and NtCreateSection). + */ + supR3HardenedWinInit(0, false /*fAvastKludge*/); + + /* + * Open the driver. + */ + if (cArgs >= 1 && suplibHardenedStrCmp(papszArgs[0], SUPR3_RESPAWN_1_ARG0) == 0) + { + SUP_DPRINTF(("supR3HardenedVmProcessInit: Opening vboxsup stub...\n")); + supR3HardenedWinOpenStubDevice(); + g_enmSupR3HardenedMainState = SUPR3HARDENEDMAINSTATE_WIN_EARLY_STUB_DEVICE_OPENED; + } + else if (cArgs >= 1 && suplibHardenedStrCmp(papszArgs[0], SUPR3_RESPAWN_2_ARG0) == 0) + { + SUP_DPRINTF(("supR3HardenedVmProcessInit: Opening vboxsup...\n")); + supR3HardenedMainOpenDevice(); + g_enmSupR3HardenedMainState = SUPR3HARDENEDMAINSTATE_WIN_EARLY_REAL_DEVICE_OPENED; + } + else + supR3HardenedFatal("Unexpected first argument '%s'!\n", papszArgs[0]); + + /* + * Reinstall the NtDll patches since there is a slight possibility that + * someone undid them while we where busy opening the device. + */ + supR3HardenedWinReInstallHooks(false /*fFirstCall */); + + /* + * Restore the LdrInitializeThunk code so we can initialize the process + * normally when we return. + */ + SUP_DPRINTF(("supR3HardenedVmProcessInit: Restoring LdrInitializeThunk...\n")); + PSUPHNTLDRCACHEENTRY pLdrEntry; + int rc = supHardNtLdrCacheOpen("ntdll.dll", &pLdrEntry, RTErrInfoInitStatic(&ErrInfo)); + if (RT_FAILURE(rc)) + supR3HardenedFatal("supR3HardenedVmProcessInit: supHardNtLdrCacheOpen failed on NTDLL: %Rrc %s\n", + rc, ErrInfo.Core.pszMsg); + + uint8_t *pbBits; + rc = supHardNtLdrCacheEntryGetBits(pLdrEntry, &pbBits, uNtDllAddr, NULL, NULL, RTErrInfoInitStatic(&ErrInfo)); + if (RT_FAILURE(rc)) + supR3HardenedFatal("supR3HardenedVmProcessInit: supHardNtLdrCacheEntryGetBits failed on NTDLL: %Rrc %s\n", + rc, ErrInfo.Core.pszMsg); + + RTLDRADDR uValue; + rc = RTLdrGetSymbolEx(pLdrEntry->hLdrMod, pbBits, uNtDllAddr, UINT32_MAX, "LdrInitializeThunk", &uValue); + if (RT_FAILURE(rc)) + supR3HardenedFatal("supR3HardenedVmProcessInit: Failed to find LdrInitializeThunk (%Rrc).\n", rc); + + PVOID pvLdrInitThunk = (PVOID)(uintptr_t)uValue; + SUPR3HARDENED_ASSERT_NT_SUCCESS(supR3HardenedWinProtectMemory(pvLdrInitThunk, 16, PAGE_EXECUTE_READWRITE)); + memcpy(pvLdrInitThunk, pbBits + ((uintptr_t)uValue - uNtDllAddr), 16); + SUPR3HARDENED_ASSERT_NT_SUCCESS(supR3HardenedWinProtectMemory(pvLdrInitThunk, 16, PAGE_EXECUTE_READ)); + + SUP_DPRINTF(("supR3HardenedVmProcessInit: Returning to LdrInitializeThunk...\n")); + return (uintptr_t)pvLdrInitThunk; +} + diff --git a/src/VBox/HostDrivers/Support/win/SUPR3HardenedMainA-win.asm b/src/VBox/HostDrivers/Support/win/SUPR3HardenedMainA-win.asm new file mode 100644 index 00000000..752eddbe --- /dev/null +++ b/src/VBox/HostDrivers/Support/win/SUPR3HardenedMainA-win.asm @@ -0,0 +1,404 @@ +; $Id: SUPR3HardenedMainA-win.asm $ +;; @file +; VirtualBox Support Library - Hardened main(), Windows assembly bits. +; + +; +; Copyright (C) 2012-2023 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 <https://www.gnu.org/licenses>. +; +; 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 * +;******************************************************************************* +%define RT_ASM_WITH_SEH64 +%include "iprt/asmdefs.mac" + + +; External code. +extern NAME(supR3HardenedEarlyProcessInit) +extern NAME(supR3HardenedMonitor_KiUserApcDispatcher_C) +%ifndef VBOX_WITHOUT_HARDENDED_XCPT_LOGGING +extern NAME(supR3HardenedMonitor_KiUserExceptionDispatcher_C) +%endif + + +BEGINCODE + + +;; +; Alternative code for LdrInitializeThunk that performs the early process startup +; for the Stub and VM processes. +; +; This does not concern itself with any arguments on stack or in registers that +; may be passed to the LdrIntializeThunk routine as we just save and restore +; them all before we restart the restored LdrInitializeThunk routine. +; +; @sa supR3HardenedEarlyProcessInit +; +BEGINPROC supR3HardenedEarlyProcessInitThunk + ; + ; Prologue. + ; + + ; Reserve space for the "return" address. + push 0 + + ; Create a stack frame, saving xBP. + push xBP + SEH64_PUSH_xBP + mov xBP, xSP + SEH64_SET_FRAME_xBP 0 ; probably wrong... + + ; Save all volatile registers. + push xAX + push xCX + push xDX +%ifdef RT_ARCH_AMD64 + push r8 + push r9 + push r10 + push r11 +%endif + + ; Reserve spill space and align the stack. + sub xSP, 20h + and xSP, ~0fh + SEH64_END_PROLOGUE + + ; + ; Call the C/C++ code that does the actual work. This returns the + ; resume address in xAX, which we put in the "return" stack position. + ; + call NAME(supR3HardenedEarlyProcessInit) + mov [xBP + xCB], xAX + + ; + ; Restore volatile registers. + ; + mov xAX, [xBP - xCB*1] + mov xCX, [xBP - xCB*2] + mov xDX, [xBP - xCB*3] +%ifdef RT_ARCH_AMD64 + mov r8, [xBP - xCB*4] + mov r9, [xBP - xCB*5] + mov r10, [xBP - xCB*6] + mov r11, [xBP - xCB*7] +%endif + ; + ; Use the leave instruction to restore xBP and set up xSP to point at + ; the resume address. Then use the 'ret' instruction to resume process + ; initializaton. + ; + leave + ret +ENDPROC supR3HardenedEarlyProcessInitThunk + + +;; +; Hook for KiUserApcDispatcher that validates user APC calls during early process +; init to prevent calls going to or referring to executable memory we've freed +; already. +; +; We just call C code here, just like supR3HardenedEarlyProcessInitThunk does. +; +; @sa supR3HardenedMonitor_KiUserApcDispatcher_C +; +BEGINPROC supR3HardenedMonitor_KiUserApcDispatcher + ; + ; Prologue. + ; + + ; Reserve space for the "return" address. + push 0 + + ; Create a stack frame, saving xBP. + push xBP + SEH64_PUSH_xBP + mov xBP, xSP + SEH64_SET_FRAME_xBP 0 ; probably wrong... + + ; Save all volatile registers. + push xAX + push xCX + push xDX +%ifdef RT_ARCH_AMD64 + push r8 + push r9 + push r10 + push r11 +%endif + + ; Reserve spill space and align the stack. + sub xSP, 20h + and xSP, ~0fh + SEH64_END_PROLOGUE + + ; + ; Call the C/C++ code that does the actual work. This returns the + ; resume address in xAX, which we put in the "return" stack position. + ; + ; On AMD64, a CONTEXT structure is found at our RSP address when we're called. + ; On x86, there a 16 byte structure containing the two routines and their + ; arguments followed by a CONTEXT structure. + ; + lea xCX, [xBP + xCB + xCB] +%ifdef RT_ARCH_X86 + mov [xSP], xCX +%endif + call NAME(supR3HardenedMonitor_KiUserApcDispatcher_C) + mov [xBP + xCB], xAX + + ; + ; Restore volatile registers. + ; + mov xAX, [xBP - xCB*1] + mov xCX, [xBP - xCB*2] + mov xDX, [xBP - xCB*3] +%ifdef RT_ARCH_AMD64 + mov r8, [xBP - xCB*4] + mov r9, [xBP - xCB*5] + mov r10, [xBP - xCB*6] + mov r11, [xBP - xCB*7] +%endif + ; + ; Use the leave instruction to restore xBP and set up xSP to point at + ; the resume address. Then use the 'ret' instruction to execute the + ; original KiUserApcDispatcher code as if we've never been here... + ; + leave + ret +ENDPROC supR3HardenedMonitor_KiUserApcDispatcher + + +%ifndef VBOX_WITHOUT_HARDENDED_XCPT_LOGGING +;; +; Hook for KiUserExceptionDispatcher that logs exceptions. +; +; For the AMD64 variant, we're not directly intercepting the function itself, but +; patching into a Wow64 callout that's done at the very start of the routine. RCX +; and RDX are set to PEXCEPTION_RECORD and PCONTEXT respectively and there is a +; return address. Also, we don't need to do any return-via-copied-out-code stuff. +; +; For X86 we hook the function and have PEXCEPTION_RECORD and PCONTEXT pointers on +; the stack, but no return address. + +; We just call C code here, just like supR3HardenedEarlyProcessInitThunk and +; supR3HardenedMonitor_KiUserApcDispatcher does. +; +; @sa supR3HardenedMonitor_KiUserExceptionDispatcher_C +; +BEGINPROC supR3HardenedMonitor_KiUserExceptionDispatcher + ; + ; Prologue. + ; + + %ifndef RT_ARCH_AMD64 + ; Reserve space for the "return" address. + push 0 + %endif + + ; Create a stack frame, saving xBP. + push xBP + SEH64_PUSH_xBP + mov xBP, xSP + SEH64_SET_FRAME_xBP 0 ; probably wrong... + + ; Save all volatile registers. + push xAX + push xCX + push xDX + %ifdef RT_ARCH_AMD64 + push r8 + push r9 + push r10 + push r11 + %endif + + ; Reserve spill space and align the stack. + sub xSP, 20h + and xSP, ~0fh + SEH64_END_PROLOGUE + + ; + ; Call the C/C++ code that does the actual work. For x86 this returns + ; the resume address in xAX, which we put in the "return" stack position. + ; + ; On both AMD64 and X86 we have two parameters on the stack that we + ; passes along to the C code (see function description for details). + ; + %ifdef RT_ARCH_X86 + mov xCX, [xBP + xCB*2] + mov xDX, [xBP + xCB*3] + mov [xSP], xCX + mov [xSP+4], xDX + %endif + call NAME(supR3HardenedMonitor_KiUserExceptionDispatcher_C) + %ifdef RT_ARCH_X86 + mov [xBP + xCB], xAX + %endif + + ; + ; Restore volatile registers. + ; + mov xAX, [xBP - xCB*1] + mov xCX, [xBP - xCB*2] + mov xDX, [xBP - xCB*3] + %ifdef RT_ARCH_AMD64 + mov r8, [xBP - xCB*4] + mov r9, [xBP - xCB*5] + mov r10, [xBP - xCB*6] + mov r11, [xBP - xCB*7] + %endif + ; + ; Use the leave instruction to restore xBP and set up xSP to point at + ; the resume address. Then use the 'ret' instruction to execute the + ; original KiUserExceptionDispatcher code as if we've never been here... + ; + leave + ret +ENDPROC supR3HardenedMonitor_KiUserExceptionDispatcher +%endif ; !VBOX_WITHOUT_HARDENDED_XCPT_LOGGING + +;; +; Composes a standard call name. +%ifdef RT_ARCH_X86 + %define SUPHNTIMP_STDCALL_NAME(a,b) _ %+ a %+ @ %+ b +%else + %define SUPHNTIMP_STDCALL_NAME(a,b) NAME(a) +%endif + +;; Concats two litterals. +%define SUPHNTIMP_CONCAT(a,b) a %+ b + + +;; +; Import data and code for an API call. +; +; @param 1 The plain API name. +; @param 2 The parameter frame size on x86. Multiple of dword. +; @param 3 Non-zero expression if system call. +; @param 4 Non-zero expression if early available call +; +%define SUPHNTIMP_SYSCALL 1 +%macro SupHardNtImport 4 + ; + ; The data. + ; +BEGINDATA +global __imp_ %+ SUPHNTIMP_STDCALL_NAME(%1,%2) ; The import name used via dllimport. +__imp_ %+ SUPHNTIMP_STDCALL_NAME(%1,%2): +GLOBALNAME g_pfn %+ %1 ; The name we like to refer to. + RTCCPTR_DEF 0 +%if %3 +GLOBALNAME g_uApiNo %+ %1 + RTCCPTR_DEF 0 +%endif + + ; + ; The code: First a call stub. + ; +BEGINCODE +global SUPHNTIMP_STDCALL_NAME(%1, %2) +SUPHNTIMP_STDCALL_NAME(%1, %2): + jmp RTCCPTR_PRE [NAME(g_pfn %+ %1) xWrtRIP] + +%if %3 + ; + ; Make system calls. + ; + %ifdef RT_ARCH_AMD64 +BEGINPROC %1 %+ _SyscallType1 + SEH64_END_PROLOGUE + mov eax, [NAME(g_uApiNo %+ %1) xWrtRIP] + mov r10, rcx + syscall + ret +ENDPROC %1 %+ _SyscallType1 +BEGINPROC %1 %+ _SyscallType2 ; Introduced with build 10525 + SEH64_END_PROLOGUE + mov eax, [NAME(g_uApiNo %+ %1) xWrtRIP] + test byte [07ffe0308h], 1 ; SharedUserData!Something + mov r10, rcx + jnz .int_alternative + syscall + ret +.int_alternative: + int 2eh + ret +ENDPROC %1 %+ _SyscallType2 + %else +BEGINPROC %1 %+ _SyscallType1 + mov edx, 07ffe0300h ; SharedUserData!SystemCallStub + mov eax, [NAME(g_uApiNo %+ %1) xWrtRIP] + call dword [edx] + ret %2 +ENDPROC %1 %+ _SyscallType1 +BEGINPROC %1 %+ _SyscallType2 + push .return + mov edx, esp + mov eax, [NAME(g_uApiNo %+ %1) xWrtRIP] + sysenter + add esp, 4 +.return: + ret %2 +ENDPROC %1 %+ _SyscallType2 + %endif +%endif + +%if %4 == 0 +global NAME(SUPHNTIMP_CONCAT(%1,_Early)) +NAME(SUPHNTIMP_CONCAT(%1,_Early)): + int3 + %ifdef RT_ARCH_AMD64 + ret + %else + ret %2 + %endif +%endif +%endmacro + +%define SUPHARNT_COMMENT(a_Comment) +%define SUPHARNT_IMPORT_SYSCALL(a_Name, a_cbParamsX86) SupHardNtImport a_Name, a_cbParamsX86, SUPHNTIMP_SYSCALL, 1 +%define SUPHARNT_IMPORT_STDCALL(a_Name, a_cbParamsX86) SupHardNtImport a_Name, a_cbParamsX86, 0, 0 +%define SUPHARNT_IMPORT_STDCALL_OPTIONAL(a_Name, a_cbParamsX86) SUPHARNT_IMPORT_STDCALL(a_Name, a_cbParamsX86) +%define SUPHARNT_IMPORT_STDCALL_EARLY(a_Name, a_cbParamsX86) SupHardNtImport a_Name, a_cbParamsX86, 0, 1 +%define SUPHARNT_IMPORT_STDCALL_EARLY_OPTIONAL(a_Name, a_cbParamsX86) SUPHARNT_IMPORT_STDCALL_EARLY(a_Name, a_cbParamsX86) +%include "import-template-ntdll.h" +%include "import-template-kernel32.h" + + +; +; For simplified LdrLoadDll patching we define a special writable, readable and +; exectuable section of 4KB where we can put jump back code. +; +section .rwxpg bss execute read write align=4096 +GLOBALNAME g_abSupHardReadWriteExecPage + resb 4096 + diff --git a/src/VBox/HostDrivers/Support/win/SUPR3HardenedMainImports-win.cpp b/src/VBox/HostDrivers/Support/win/SUPR3HardenedMainImports-win.cpp new file mode 100644 index 00000000..414188a3 --- /dev/null +++ b/src/VBox/HostDrivers/Support/win/SUPR3HardenedMainImports-win.cpp @@ -0,0 +1,873 @@ +/* $Id: SUPR3HardenedMainImports-win.cpp $ */ +/** @file + * VirtualBox Support Library - Hardened Main, Windows Import Trickery. + */ + +/* + * Copyright (C) 2006-2023 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 <https://www.gnu.org/licenses>. + * + * 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 * +*********************************************************************************************************************************/ +#include <iprt/nt/nt-and-windows.h> + +#include <VBox/sup.h> +#include <VBox/err.h> +#include <iprt/ctype.h> +#include <iprt/initterm.h> +#include <iprt/param.h> +#include <iprt/string.h> +#include <iprt/utf16.h> + +#include "SUPLibInternal.h" +#include "SUPHardenedVerify-win.h" + + +/********************************************************************************************************************************* +* Defined Constants And Macros * +*********************************************************************************************************************************/ +#define SUPHARNT_COMMENT(a_Blah) /* nothing */ + +#define VBOX_HARDENED_STUB_WITHOUT_IMPORTS +#ifdef VBOX_HARDENED_STUB_WITHOUT_IMPORTS +# define SUPHNTIMP_ERROR(a_fReportErrors, a_id, a_szWhere, a_enmOp, a_rc, ...) \ + do { \ + if (a_fReportErrors) supR3HardenedFatalMsg(a_szWhere, a_enmOp, a_rc, __VA_ARGS__); \ + else { static const char s_szWhere[] = a_szWhere; *(char *)(uintptr_t)(a_id) += 1; __debugbreak(); } \ + } while (0) +#else +# define SUPHNTIMP_ERROR(a_fReportErrors, a_id, a_szWhere, a_enmOp, a_rc, ...) \ + supR3HardenedFatalMsg(a_szWhere, a_enmOp, a_rc, __VA_ARGS__) + +#endif + + +/********************************************************************************************************************************* +* Defined Constants And Macros * +*********************************************************************************************************************************/ +/** + * Import function entry. + */ +typedef struct SUPHNTIMPFUNC +{ + /** The name of the function we're importing. */ + const char *pszName; + /** Where to store the function address (think __imp_ApiName). */ + PFNRT *ppfnImport; + /** Pointer to an early dummy function for imports that aren't available + * during early process initialization. */ + PFNRT pfnEarlyDummy; + /** Indicates whether this is an optional import and failure to locate it + * should set it to NULL instead of freaking out. */ + bool fOptional; +} SUPHNTIMPFUNC; +/** Pointer to an import table entry. */ +typedef SUPHNTIMPFUNC const *PCSUPHNTIMPFUNC; + +/** + * Information for constructing a direct system call. + */ +typedef struct SUPHNTIMPSYSCALL +{ + /** Where to store the system call number. + * NULL if this import doesn't stupport direct system call. */ + uint32_t *puApiNo; + /** Assembly system call routine, type 1. */ + PFNRT pfnType1; + /** Assembly system call routine, type 2. */ + PFNRT pfnType2; +#ifdef RT_ARCH_X86 + /** The parameter size in bytes for a standard call. */ + uint32_t cbParams; +#endif +} SUPHNTIMPSYSCALL; +/** Pointer to a system call entry. */ +typedef SUPHNTIMPSYSCALL const *PCSUPHNTIMPSYSCALL; + +/** + * Import DLL. + * + * This contains both static (like name & imports) and runtime information (like + * load and export table locations). + * + * @sa RTDBGNTKRNLMODINFO + */ +typedef struct SUPHNTIMPDLL +{ + /** @name Static data. + * @{ */ + const wchar_t *pwszName; + const char *pszName; + size_t cImports; + PCSUPHNTIMPFUNC paImports; + /** Array running parallel to paImports if present. */ + PCSUPHNTIMPSYSCALL paSyscalls; + /** @} */ + + + /** The image base. */ + uint8_t const *pbImageBase; + /** The NT headers. */ + PIMAGE_NT_HEADERS pNtHdrs; + /** The NT header offset/RVA. */ + uint32_t offNtHdrs; + /** The end of the section headers. */ + uint32_t offEndSectHdrs; + /** The end of the image. */ + uint32_t cbImage; + /** Offset of the export directory. */ + uint32_t offExportDir; + /** Size of the export directory. */ + uint32_t cbExportDir; + + /** Exported functions and data by ordinal (RVAs). */ + uint32_t const *paoffExports; + /** The number of exports. */ + uint32_t cExports; + /** The number of exported names. */ + uint32_t cNamedExports; + /** Pointer to the array of exported names (RVAs to strings). */ + uint32_t const *paoffNamedExports; + /** Array parallel to paoffNamedExports with the corresponding ordinals + * (indexes into paoffExports). */ + uint16_t const *pau16NameOrdinals; + + /** Number of patched export table entries. */ + uint32_t cPatchedExports; + +} SUPHNTIMPDLL; +/** Pointer to an import DLL entry. */ +typedef SUPHNTIMPDLL *PSUPHNTIMPDLL; + + + +/* + * Declare assembly symbols. + */ +#define SUPHARNT_IMPORT_STDCALL_EARLY(a_Name, a_cbParamsX86) \ + extern PFNRT RT_CONCAT(g_pfn, a_Name); +#define SUPHARNT_IMPORT_STDCALL_EARLY_OPTIONAL(a_Name, a_cbParamsX86) SUPHARNT_IMPORT_STDCALL_EARLY(a_Name, a_cbParamsX86) +#define SUPHARNT_IMPORT_SYSCALL(a_Name, a_cbParamsX86) \ + SUPHARNT_IMPORT_STDCALL_EARLY(a_Name, a_cbParamsX86) \ + extern uint32_t RT_CONCAT(g_uApiNo, a_Name); \ + extern FNRT RT_CONCAT(a_Name, _SyscallType1); \ + extern FNRT RT_CONCAT(a_Name, _SyscallType2); +#define SUPHARNT_IMPORT_STDCALL(a_Name, a_cbParamsX86) \ + extern PFNRT RT_CONCAT(g_pfn, a_Name); \ + extern FNRT RT_CONCAT(a_Name, _Early); +#define SUPHARNT_IMPORT_STDCALL_OPTIONAL(a_Name, a_cbParamsX86) SUPHARNT_IMPORT_STDCALL(a_Name, a_cbParamsX86) + +RT_C_DECLS_BEGIN +#include "import-template-ntdll.h" +#include "import-template-kernel32.h" +RT_C_DECLS_END + +/* + * Import functions. + */ +#undef SUPHARNT_IMPORT_SYSCALL +#undef SUPHARNT_IMPORT_STDCALL_EARLY +#undef SUPHARNT_IMPORT_STDCALL_EARLY_OPTIONAL +#undef SUPHARNT_IMPORT_STDCALL +#undef SUPHARNT_IMPORT_STDCALL_OPTIONAL +#define SUPHARNT_IMPORT_SYSCALL(a_Name, a_cbParamsX86) \ + { #a_Name, &RT_CONCAT(g_pfn, a_Name), NULL, false }, +#define SUPHARNT_IMPORT_STDCALL_EARLY(a_Name, a_cbParamsX86) \ + { #a_Name, &RT_CONCAT(g_pfn, a_Name), NULL, false }, +#define SUPHARNT_IMPORT_STDCALL_EARLY_OPTIONAL(a_Name, a_cbParamsX86) \ + { #a_Name, &RT_CONCAT(g_pfn, a_Name), NULL, true }, +#define SUPHARNT_IMPORT_STDCALL(a_Name, a_cbParamsX86) \ + { #a_Name, &RT_CONCAT(g_pfn, a_Name), RT_CONCAT(a_Name,_Early), false }, +#define SUPHARNT_IMPORT_STDCALL_OPTIONAL(a_Name, a_cbParamsX86) \ + { #a_Name, &RT_CONCAT(g_pfn, a_Name), RT_CONCAT(a_Name,_Early), true }, +static const SUPHNTIMPFUNC g_aSupNtImpNtDllFunctions[] = +{ +#include "import-template-ntdll.h" +}; + +static const SUPHNTIMPFUNC g_aSupNtImpKernel32Functions[] = +{ +#include "import-template-kernel32.h" +}; + + + +/* + * Syscalls in ntdll. + */ +#undef SUPHARNT_IMPORT_SYSCALL +#undef SUPHARNT_IMPORT_STDCALL_EARLY +#undef SUPHARNT_IMPORT_STDCALL_EARLY_OPTIONAL +#undef SUPHARNT_IMPORT_STDCALL +#undef SUPHARNT_IMPORT_STDCALL_OPTIONAL +#ifdef RT_ARCH_AMD64 +# define SUPHARNT_IMPORT_STDCALL(a_Name, a_cbParamsX86) \ + { NULL, NULL }, +# define SUPHARNT_IMPORT_SYSCALL(a_Name, a_cbParamsX86) \ + { &RT_CONCAT(g_uApiNo, a_Name), &RT_CONCAT(a_Name, _SyscallType1), &RT_CONCAT(a_Name, _SyscallType2) }, +#elif defined(RT_ARCH_X86) +# define SUPHARNT_IMPORT_STDCALL(a_Name, a_cbParamsX86) \ + { NULL, NULL, NULL, 0 }, +# define SUPHARNT_IMPORT_SYSCALL(a_Name, a_cbParamsX86) \ + { &RT_CONCAT(g_uApiNo, a_Name), &RT_CONCAT(a_Name,_SyscallType1), &RT_CONCAT(a_Name, _SyscallType2), a_cbParamsX86 }, +#endif +#define SUPHARNT_IMPORT_STDCALL_OPTIONAL(a_Name, a_cbParamsX86) SUPHARNT_IMPORT_STDCALL(a_Name, a_cbParamsX86) +#define SUPHARNT_IMPORT_STDCALL_EARLY(a_Name, a_cbParamsX86) SUPHARNT_IMPORT_STDCALL(a_Name, a_cbParamsX86) +#define SUPHARNT_IMPORT_STDCALL_EARLY_OPTIONAL(a_Name, a_cbParamsX86) SUPHARNT_IMPORT_STDCALL(a_Name, a_cbParamsX86) +static const SUPHNTIMPSYSCALL g_aSupNtImpNtDllSyscalls[] = +{ +#include "import-template-ntdll.h" +}; + + +/** + * All the DLLs we import from. + * @remarks Code ASSUMES that ntdll is the first entry. + */ +static SUPHNTIMPDLL g_aSupNtImpDlls[] = +{ + { L"ntdll.dll", "ntdll.dll", RT_ELEMENTS(g_aSupNtImpNtDllFunctions), g_aSupNtImpNtDllFunctions, g_aSupNtImpNtDllSyscalls }, + { L"kernelbase.dll", "kernelbase.dll", 0 /* optional module, forwarders only */, NULL, NULL }, + { L"kernel32.dll", "kernel32.dll", RT_ELEMENTS(g_aSupNtImpKernel32Functions), g_aSupNtImpKernel32Functions, NULL }, +}; + + +static void supR3HardenedFindOrLoadModule(PSUPHNTIMPDLL pDll) +{ +#ifdef VBOX_HARDENED_STUB_WITHOUT_IMPORTS + uint32_t const cbName = (uint32_t)RTUtf16Len(pDll->pwszName) * sizeof(WCHAR); + PPEB_LDR_DATA pLdrData = NtCurrentPeb()->Ldr; + LIST_ENTRY *pList = &pLdrData->InMemoryOrderModuleList; + LIST_ENTRY *pListEntry = pList->Flink; + uint32_t cLoops = 0; + while (pListEntry != pList && cLoops < 1024) + { + PLDR_DATA_TABLE_ENTRY pLdrEntry = RT_FROM_MEMBER(pListEntry, LDR_DATA_TABLE_ENTRY, InMemoryOrderLinks); + + if ( pLdrEntry->FullDllName.Length > cbName + sizeof(WCHAR) + && ( pLdrEntry->FullDllName.Buffer[(pLdrEntry->FullDllName.Length - cbName) / sizeof(WCHAR) - 1] == '\\' + || pLdrEntry->FullDllName.Buffer[(pLdrEntry->FullDllName.Length - cbName) / sizeof(WCHAR) - 1] == '/') + && RTUtf16ICmpAscii(&pLdrEntry->FullDllName.Buffer[(pLdrEntry->FullDllName.Length - cbName) / sizeof(WCHAR)], + pDll->pszName) == 0) + { + pDll->pbImageBase = (uint8_t *)pLdrEntry->DllBase; + return; + } + + pListEntry = pListEntry->Flink; + cLoops++; + } + + if (!pDll->cImports) + pDll->pbImageBase = NULL; /* optional */ + else + SUPHNTIMP_ERROR(false, 1, "supR3HardenedFindOrLoadModule", kSupInitOp_Misc, VERR_MODULE_NOT_FOUND, + "Failed to locate %ls", pDll->pwszName); +#else + HMODULE hmod = GetModuleHandleW(pDll->pwszName); + if (RT_UNLIKELY(!hmod && pDll->cImports)) + SUPHNTIMP_ERROR(true, 1, "supR3HardenedWinInitImports", kSupInitOp_Misc, VERR_MODULE_NOT_FOUND, + "Failed to locate %ls", pDll->pwszName); + pDll->pbImageBase = (uint8_t *)hmod; +#endif +} + + +/** @sa rtR0DbgKrnlNtParseModule */ +static void supR3HardenedParseModule(PSUPHNTIMPDLL pDll) +{ + /* + * Locate the PE header, do some basic validations. + */ + IMAGE_DOS_HEADER const *pMzHdr = (IMAGE_DOS_HEADER const *)pDll->pbImageBase; + uint32_t offNtHdrs = 0; + PIMAGE_NT_HEADERS pNtHdrs; + if (pMzHdr->e_magic == IMAGE_DOS_SIGNATURE) + { + offNtHdrs = pMzHdr->e_lfanew; + if (offNtHdrs > _2K) + SUPHNTIMP_ERROR(false, 2, "supR3HardenedParseModule", kSupInitOp_Misc, VERR_MODULE_NOT_FOUND, + "%ls: e_lfanew=%#x, expected a lower value", pDll->pwszName, offNtHdrs); + } + pDll->pNtHdrs = pNtHdrs = (PIMAGE_NT_HEADERS)&pDll->pbImageBase[offNtHdrs]; + + if (pNtHdrs->Signature != IMAGE_NT_SIGNATURE) + SUPHNTIMP_ERROR(false, 3, "supR3HardenedParseModule", kSupInitOp_Misc, VERR_INVALID_EXE_SIGNATURE, + "%ls: Invalid PE signature: %#x", pDll->pwszName, pNtHdrs->Signature); + if (pNtHdrs->FileHeader.SizeOfOptionalHeader != sizeof(pNtHdrs->OptionalHeader)) + SUPHNTIMP_ERROR(false, 4, "supR3HardenedParseModule", kSupInitOp_Misc, VERR_INVALID_EXE_SIGNATURE, + "%ls: Unexpected optional header size: %#x", pDll->pwszName, pNtHdrs->FileHeader.SizeOfOptionalHeader); + if (pNtHdrs->OptionalHeader.Magic != RT_CONCAT3(IMAGE_NT_OPTIONAL_HDR,ARCH_BITS,_MAGIC)) + SUPHNTIMP_ERROR(false, 5, "supR3HardenedParseModule", kSupInitOp_Misc, VERR_INVALID_EXE_SIGNATURE, + "%ls: Unexpected optional header magic: %#x", pDll->pwszName, pNtHdrs->OptionalHeader.Magic); + if (pNtHdrs->OptionalHeader.NumberOfRvaAndSizes != IMAGE_NUMBEROF_DIRECTORY_ENTRIES) + SUPHNTIMP_ERROR(false, 6, "supR3HardenedParseModule", kSupInitOp_Misc, VERR_INVALID_EXE_SIGNATURE, + "%ls: Unexpected number of RVA and sizes: %#x", pDll->pwszName, pNtHdrs->OptionalHeader.NumberOfRvaAndSizes); + + pDll->offNtHdrs = offNtHdrs; + pDll->offEndSectHdrs = offNtHdrs + + sizeof(*pNtHdrs) + + pNtHdrs->FileHeader.NumberOfSections * sizeof(IMAGE_SECTION_HEADER); + pDll->cbImage = pNtHdrs->OptionalHeader.SizeOfImage; + + /* + * Find the export directory. + */ + IMAGE_DATA_DIRECTORY ExpDir = pNtHdrs->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT]; + if ( ExpDir.Size < sizeof(IMAGE_EXPORT_DIRECTORY) + || ExpDir.VirtualAddress < pDll->offEndSectHdrs + || ExpDir.VirtualAddress >= pNtHdrs->OptionalHeader.SizeOfImage + || ExpDir.VirtualAddress + ExpDir.Size > pNtHdrs->OptionalHeader.SizeOfImage) + SUPHNTIMP_ERROR(false, 7, "supR3HardenedParseModule", kSupInitOp_Misc, VERR_INVALID_EXE_SIGNATURE, + "%ls: Missing or invalid export directory: %#lx LB %#x", pDll->pwszName, ExpDir.VirtualAddress, ExpDir.Size); + pDll->offExportDir = ExpDir.VirtualAddress; + pDll->cbExportDir = ExpDir.Size; + + IMAGE_EXPORT_DIRECTORY const *pExpDir = (IMAGE_EXPORT_DIRECTORY const *)&pDll->pbImageBase[ExpDir.VirtualAddress]; + + if ( pExpDir->NumberOfFunctions >= _1M + || pExpDir->NumberOfFunctions < 1 + || pExpDir->NumberOfNames >= _1M + || pExpDir->NumberOfNames < 1) + SUPHNTIMP_ERROR(false, 8, "supR3HardenedParseModule", kSupInitOp_Misc, VERR_INVALID_EXE_SIGNATURE, + "%ls: NumberOfNames or/and NumberOfFunctions are outside the expected range: nof=%#x non=%#x\n", + pDll->pwszName, pExpDir->NumberOfFunctions, pExpDir->NumberOfNames); + pDll->cNamedExports = pExpDir->NumberOfNames; + pDll->cExports = RT_MAX(pExpDir->NumberOfNames, pExpDir->NumberOfFunctions); + + if ( pExpDir->AddressOfFunctions < pDll->offEndSectHdrs + || pExpDir->AddressOfFunctions >= pNtHdrs->OptionalHeader.SizeOfImage + || pExpDir->AddressOfFunctions + pDll->cExports * sizeof(uint32_t) > pNtHdrs->OptionalHeader.SizeOfImage) + SUPHNTIMP_ERROR(false, 9, "supR3HardenedParseModule", kSupInitOp_Misc, VERR_INVALID_EXE_SIGNATURE, + "%ls: Bad AddressOfFunctions: %#x\n", pDll->pwszName, pExpDir->AddressOfFunctions); + pDll->paoffExports = (uint32_t const *)&pDll->pbImageBase[pExpDir->AddressOfFunctions]; + + if ( pExpDir->AddressOfNames < pDll->offEndSectHdrs + || pExpDir->AddressOfNames >= pNtHdrs->OptionalHeader.SizeOfImage + || pExpDir->AddressOfNames + pExpDir->NumberOfNames * sizeof(uint32_t) > pNtHdrs->OptionalHeader.SizeOfImage) + SUPHNTIMP_ERROR(false, 10, "supR3HardenedParseModule", kSupInitOp_Misc, VERR_INVALID_EXE_SIGNATURE, + "%ls: Bad AddressOfNames: %#x\n", pDll->pwszName, pExpDir->AddressOfNames); + pDll->paoffNamedExports = (uint32_t const *)&pDll->pbImageBase[pExpDir->AddressOfNames]; + + if ( pExpDir->AddressOfNameOrdinals < pDll->offEndSectHdrs + || pExpDir->AddressOfNameOrdinals >= pNtHdrs->OptionalHeader.SizeOfImage + || pExpDir->AddressOfNameOrdinals + pExpDir->NumberOfNames * sizeof(uint32_t) > pNtHdrs->OptionalHeader.SizeOfImage) + SUPHNTIMP_ERROR(false, 11, "supR3HardenedParseModule", kSupInitOp_Misc, VERR_INVALID_EXE_SIGNATURE, + "%ls: Bad AddressOfNameOrdinals: %#x\n", pDll->pwszName, pExpDir->AddressOfNameOrdinals); + pDll->pau16NameOrdinals = (uint16_t const *)&pDll->pbImageBase[pExpDir->AddressOfNameOrdinals]; +} + + +/** @sa rtR0DbgKrnlInfoLookupSymbol */ +static const char *supR3HardenedResolveImport(PSUPHNTIMPDLL pDll, PCSUPHNTIMPFUNC pImport, bool fReportErrors) +{ + /* + * Binary search. + */ + uint32_t iStart = 0; + uint32_t iEnd = pDll->cNamedExports; + while (iStart < iEnd) + { + uint32_t iCur = iStart + (iEnd - iStart) / 2; + uint32_t offExpName = pDll->paoffNamedExports[iCur]; + if (RT_UNLIKELY(offExpName < pDll->offEndSectHdrs || offExpName >= pDll->cbImage)) + SUPHNTIMP_ERROR(fReportErrors, 12, "supR3HardenedResolveImport", kSupInitOp_Misc, VERR_SYMBOL_NOT_FOUND, + "%ls: Bad export name entry: %#x (iCur=%#x)", pDll->pwszName, offExpName, iCur); + + const char *pszExpName = (const char *)&pDll->pbImageBase[offExpName]; + int iDiff = strcmp(pszExpName, pImport->pszName); + if (iDiff > 0) /* pszExpName > pszSymbol: search chunck before i */ + iEnd = iCur; + else if (iDiff < 0) /* pszExpName < pszSymbol: search chunk after i */ + iStart = iCur + 1; + else /* pszExpName == pszSymbol */ + { + uint16_t iExpOrdinal = pDll->pau16NameOrdinals[iCur]; + if (iExpOrdinal < pDll->cExports) + { + uint32_t offExport = pDll->paoffExports[iExpOrdinal]; + + /* detect export table patching. */ + if (offExport >= pDll->cbImage) + pDll->cPatchedExports++; + + if (offExport - pDll->offExportDir >= pDll->cbExportDir) + { + *pImport->ppfnImport = (PFNRT)&pDll->pbImageBase[offExport]; + return NULL; + } + + /* Forwarder. */ + return (const char *)&pDll->pbImageBase[offExport]; + } + SUPHNTIMP_ERROR(fReportErrors, 14, "supR3HardenedResolveImport", kSupInitOp_Misc, VERR_BAD_EXE_FORMAT, + "%ls: Name ordinal for '%s' is out of bounds: %#x (max %#x)", + pDll->pwszName, iExpOrdinal, pDll->cExports); + return NULL; + } + } + + if (!pImport->fOptional) + SUPHNTIMP_ERROR(fReportErrors, 15, "supR3HardenedResolveImport", kSupInitOp_Misc, VERR_SYMBOL_NOT_FOUND, + "%ls: Failed to resolve '%s'.", pDll->pwszName, pImport->pszName); + *pImport->ppfnImport = NULL; + return NULL; +} + + +static void supR3HardenedDirectSyscall(PSUPHNTIMPDLL pDll, PCSUPHNTIMPFUNC pImport, PCSUPHNTIMPSYSCALL pSyscall, + PSUPHNTLDRCACHEENTRY pLdrEntry, uint8_t *pbBits, bool fReportErrors) +{ + /* + * Skip non-syscall entries. + */ + if (!pSyscall->puApiNo) + return; + + /* + * Locate the virgin bits. + */ + RTLDRADDR uValue; + int rc = RTLdrGetSymbolEx(pLdrEntry->hLdrMod, pbBits, (uintptr_t)pDll->pbImageBase, UINT32_MAX, pImport->pszName, &uValue); + if (RT_FAILURE(rc)) + { + SUPHNTIMP_ERROR(fReportErrors, 16, "supR3HardenedDirectSyscall", kSupInitOp_Misc, rc, + "%s: RTLdrGetSymbolEx failed on %s: %Rrc", pDll->pszName, pImport->pszName, rc); + return; + } + uintptr_t offSymbol = (uintptr_t)uValue - (uintptr_t)pDll->pbImageBase; + uint8_t const *pbFunction = &pbBits[offSymbol]; + + /* + * Parse the code and extract the API call number. + */ +#ifdef RT_ARCH_AMD64 + /* Pattern #1: XP64/W2K3-64 thru Windows 10 build 10240. + 0:000> u ntdll!NtCreateSection + ntdll!NtCreateSection: + 00000000`779f1750 4c8bd1 mov r10,rcx + 00000000`779f1753 b847000000 mov eax,47h + 00000000`779f1758 0f05 syscall + 00000000`779f175a c3 ret + 00000000`779f175b 0f1f440000 nop dword ptr [rax+rax] + + Pattern #2: Windows 10 build 10525+. + 0:000> u ntdll_7ffc26300000!NtCreateSection + ntdll_7ffc26300000!ZwCreateSection: + 00007ffc`263943e0 4c8bd1 mov r10,rcx + 00007ffc`263943e3 b84a000000 mov eax,4Ah + 00007ffc`263943e8 f604250803fe7f01 test byte ptr [SharedUserData+0x308 (00000000`7ffe0308)],1 + 00007ffc`263943f0 7503 jne ntdll_7ffc26300000!ZwCreateSection+0x15 (00007ffc`263943f5) + 00007ffc`263943f2 0f05 syscall + 00007ffc`263943f4 c3 ret + 00007ffc`263943f5 cd2e int 2Eh + 00007ffc`263943f7 c3 ret + */ + if ( pbFunction[ 0] == 0x4c /* mov r10, rcx */ + && pbFunction[ 1] == 0x8b + && pbFunction[ 2] == 0xd1 + && pbFunction[ 3] == 0xb8 /* mov eax, 0000yyzzh */ + //&& pbFunction[ 4] == 0xZZ + //&& pbFunction[ 5] == 0xYY + && pbFunction[ 6] == 0x00 + && pbFunction[ 7] == 0x00) + { + if ( pbFunction[ 8] == 0x0f /* syscall */ + && pbFunction[ 9] == 0x05 + && pbFunction[10] == 0xc3 /* ret */ ) + { + *pSyscall->puApiNo = RT_MAKE_U16(pbFunction[4], pbFunction[5]); + *pImport->ppfnImport = pSyscall->pfnType1; + return; + } + if ( pbFunction[ 8] == 0xf6 /* test byte ptr [SharedUserData+0x308 (00000000`7ffe0308)],1 */ + && pbFunction[ 9] == 0x04 + && pbFunction[10] == 0x25 + && pbFunction[11] == 0x08 + && pbFunction[12] == 0x03 + && pbFunction[13] == 0xfe + && pbFunction[14] == 0x7f + && pbFunction[15] == 0x01 + && pbFunction[16] == 0x75 /* jnz +3 */ + && pbFunction[17] == 0x03 + && pbFunction[18] == 0x0f /* syscall*/ + && pbFunction[19] == 0x05 + && pbFunction[20] == 0xc3 /* ret */ + && pbFunction[21] == 0xcd /* int 2eh */ + && pbFunction[22] == 0x2e + && pbFunction[23] == 0xc3 /* ret */ ) + { + *pSyscall->puApiNo = RT_MAKE_U16(pbFunction[4], pbFunction[5]); + *pImport->ppfnImport = pSyscall->pfnType2; + return; + } + } +#else + /* Pattern #1: XP thru Windows 7 + kd> u ntdll!NtCreateSection + ntdll!NtCreateSection: + 7c90d160 b832000000 mov eax,32h + 7c90d165 ba0003fe7f mov edx,offset SharedUserData!SystemCallStub (7ffe0300) + 7c90d16a ff12 call dword ptr [edx] + 7c90d16c c21c00 ret 1Ch + 7c90d16f 90 nop + The variable bit is the value loaded into eax: XP=32h, W2K3=34h, Vista=4bh, W7=54h + + Pattern #2: Windows 8.1 + 0:000:x86> u ntdll_6a0f0000!NtCreateSection + ntdll_6a0f0000!NtCreateSection: + 6a15eabc b854010000 mov eax,154h + 6a15eac1 e803000000 call ntdll_6a0f0000!NtCreateSection+0xd (6a15eac9) + 6a15eac6 c21c00 ret 1Ch + 6a15eac9 8bd4 mov edx,esp + 6a15eacb 0f34 sysenter + 6a15eacd c3 ret + The variable bit is the value loaded into eax: W81=154h + Note! One nice thing here is that we can share code pattern #1. */ + + if ( pbFunction[ 0] == 0xb8 /* mov eax, 0000yyzzh*/ + //&& pbFunction[ 1] <= 0xZZ + //&& pbFunction[ 2] <= 0xYY + && pbFunction[ 3] == 0x00 + && pbFunction[ 4] == 0x00) + { + *pSyscall->puApiNo = RT_MAKE_U16(pbFunction[1], pbFunction[2]); + if ( pbFunction[5] == 0xba /* mov edx, offset SharedUserData!SystemCallStub */ + && pbFunction[ 6] == 0x00 + && pbFunction[ 7] == 0x03 + && pbFunction[ 8] == 0xfe + && pbFunction[ 9] == 0x7f + && pbFunction[10] == 0xff /* call [edx] */ + && pbFunction[11] == 0x12 + && ( ( pbFunction[12] == 0xc2 /* ret 1ch */ + && pbFunction[13] == pSyscall->cbParams + && pbFunction[14] == 0x00) + || ( pbFunction[12] == 0xc3 /* ret */ + && pSyscall->cbParams == 0) + ) + ) + { + *pImport->ppfnImport = pSyscall->pfnType1; + return; + } + + if ( pbFunction[ 5] == 0xe8 /* call [$+3] */ + && RT_ABS(*(int32_t *)&pbFunction[6]) < 0x10 + && ( ( pbFunction[10] == 0xc2 /* ret 1ch */ + && pbFunction[11] == pSyscall->cbParams + && pbFunction[12] == 0x00) + || ( pbFunction[10] == 0xc3 /* ret */ + && pSyscall->cbParams == 0) + ) + ) + { + *pImport->ppfnImport = pSyscall->pfnType2; + return; + } + } +#endif + + /* + * Failed to parse it. + */ + volatile uint8_t abCopy[16]; + memcpy((void *)&abCopy[0], pbFunction, sizeof(abCopy)); + SUPHNTIMP_ERROR(fReportErrors, 17, "supR3HardenedWinInitImports", kSupInitOp_Misc, rc, + "%ls: failed to parse syscall: '%s': %.16Rhxs", + pDll->pwszName, pImport->pszName, &abCopy[0]); +} + + +/** + * Check out system calls and do the directly instead of via NtDll. + * + * We need to have access to the on disk NTDLL.DLL file as we do not trust the + * stuff we find in memory. Too early to verify signatures though. + * + * @param fReportErrors Whether we've got the machinery for reporting + * errors going already. + * @param pErrInfo Buffer for gathering additional error info. This + * is mainly to avoid consuming lots of stacks with + * RTERRINFOSTATIC structures. + */ +DECLHIDDEN(void) supR3HardenedWinInitSyscalls(bool fReportErrors, PRTERRINFO pErrInfo) +{ + for (uint32_t iDll = 0; iDll < RT_ELEMENTS(g_aSupNtImpDlls); iDll++) + if (g_aSupNtImpDlls[iDll].paSyscalls) + { + PSUPHNTLDRCACHEENTRY pLdrEntry; + int rc = supHardNtLdrCacheOpen(g_aSupNtImpDlls[iDll].pszName, &pLdrEntry, pErrInfo); + if (RT_SUCCESS(rc)) + { + uint8_t *pbBits; + rc = supHardNtLdrCacheEntryGetBits(pLdrEntry, &pbBits, (uintptr_t)g_aSupNtImpDlls[iDll].pbImageBase, + NULL, NULL, pErrInfo); + if (RT_SUCCESS(rc)) + { + for (uint32_t i = 0; i < g_aSupNtImpDlls[iDll].cImports; i++) + supR3HardenedDirectSyscall(&g_aSupNtImpDlls[iDll], &g_aSupNtImpDlls[iDll].paImports[i], + &g_aSupNtImpDlls[iDll].paSyscalls[i], pLdrEntry, pbBits, fReportErrors); + } + else + SUPHNTIMP_ERROR(fReportErrors, 20, "supR3HardenedWinInitImports", kSupInitOp_Misc, rc, + "%ls: supHardNtLdrCacheEntryGetBits failed: %Rrc %s", + g_aSupNtImpDlls[iDll].pwszName, rc, pErrInfo ? pErrInfo->pszMsg : ""); + } + else + SUPHNTIMP_ERROR(fReportErrors, 21, "supR3HardenedWinInitImports", kSupInitOp_Misc, rc, + "%ls: supHardNtLdrCacheOpen failed: %Rrc %s", + g_aSupNtImpDlls[iDll].pwszName, rc, pErrInfo ? pErrInfo->pszMsg : ""); + } +} + + +/** + * Resolves a few NtDll functions we need before child purification is executed. + * + * We must not permanently modify any global data here. + * + * @param uNtDllAddr The address of the NTDLL. + * @param ppfnNtWaitForSingleObject Where to store the NtWaitForSingleObject + * address. + * @param ppfnNtSetEvent Where to store the NtSetEvent address. + */ +DECLHIDDEN(void) supR3HardenedWinGetVeryEarlyImports(uintptr_t uNtDllAddr, + PFNNTWAITFORSINGLEOBJECT *ppfnNtWaitForSingleObject, + PFNNTSETEVENT *ppfnNtSetEvent) +{ + /* + * NTDLL is the first entry in the list. Save it and do the parsing. + */ + SUPHNTIMPDLL SavedDllEntry = g_aSupNtImpDlls[0]; + + g_aSupNtImpDlls[0].pbImageBase = (uint8_t const *)uNtDllAddr; + supR3HardenedParseModule(&g_aSupNtImpDlls[0]); + + /* + * Create a temporary import table for the requested APIs and resolve them. + */ + SUPHNTIMPFUNC aImports[] = + { + { "NtWaitForSingleObject", (PFNRT *)ppfnNtWaitForSingleObject, NULL, false }, + { "NtSetEvent", (PFNRT *)ppfnNtSetEvent, NULL, false }, + }; + + for (uint32_t i = 0; i < RT_ELEMENTS(aImports); i++) + { + const char *pszForwarder = supR3HardenedResolveImport(&g_aSupNtImpDlls[0], &aImports[i], false); + if (pszForwarder) + SUPHNTIMP_ERROR(false, 31, "supR3HardenedWinGetVeryEarlyImports", kSupInitOp_Misc, VERR_MODULE_NOT_FOUND, + "ntdll: Failed to resolve forwarder '%s'.", pszForwarder); + } + + /* + * Restore the NtDll entry. + */ + g_aSupNtImpDlls[0] = SavedDllEntry; +} + + +/** + * Resolves NtDll functions we can trust calling before process init. + * + * @param uNtDllAddr The address of the NTDLL. + */ +DECLHIDDEN(void) supR3HardenedWinInitImportsEarly(uintptr_t uNtDllAddr) +{ + /* + * NTDLL is the first entry in the list. + */ + g_aSupNtImpDlls[0].pbImageBase = (uint8_t const *)uNtDllAddr; + supR3HardenedParseModule(&g_aSupNtImpDlls[0]); + for (uint32_t i = 0; i < g_aSupNtImpDlls[0].cImports; i++) + if (!g_aSupNtImpDlls[0].paImports[i].pfnEarlyDummy) + { + const char *pszForwarder = supR3HardenedResolveImport(&g_aSupNtImpDlls[0], &g_aSupNtImpDlls[0].paImports[i], false); + if (pszForwarder) + SUPHNTIMP_ERROR(false, 32, "supR3HardenedWinInitImports", kSupInitOp_Misc, VERR_MODULE_NOT_FOUND, + "ntdll: Failed to resolve forwarder '%s'.", pszForwarder); + } + else + *g_aSupNtImpDlls[0].paImports[i].ppfnImport = g_aSupNtImpDlls[0].paImports[i].pfnEarlyDummy; + + /* + * Point the other imports at the early init stubs. + */ + for (uint32_t iDll = 1; iDll < RT_ELEMENTS(g_aSupNtImpDlls); iDll++) + for (uint32_t i = 0; i < g_aSupNtImpDlls[iDll].cImports; i++) + if (!g_aSupNtImpDlls[iDll].paImports[i].fOptional) + *g_aSupNtImpDlls[iDll].paImports[i].ppfnImport = g_aSupNtImpDlls[iDll].paImports[i].pfnEarlyDummy; + else + *g_aSupNtImpDlls[iDll].paImports[i].ppfnImport = NULL; +} + + +/** + * Resolves imported functions, esp. system calls from NTDLL. + * + * This crap is necessary because there are sandboxing products out there that + * will mess with system calls we make, just like any other wannabe userland + * rootkit. Kudos to microsoft for not providing a generic system call hook API + * in the kernel mode, which I guess is what forcing these kind of products to + * do ugly userland hacks that doesn't really hold water. + */ +DECLHIDDEN(void) supR3HardenedWinInitImports(void) +{ + RTERRINFOSTATIC ErrInfo; + + /* + * Find the DLLs we will be needing first (forwarders). + */ + for (uint32_t iDll = 0; iDll < RT_ELEMENTS(g_aSupNtImpDlls); iDll++) + { + supR3HardenedFindOrLoadModule(&g_aSupNtImpDlls[iDll]); + if (g_aSupNtImpDlls[iDll].pbImageBase) + supR3HardenedParseModule(&g_aSupNtImpDlls[iDll]); + } + + /* + * Resolve the functions. + */ + for (uint32_t iDll = 0; iDll < RT_ELEMENTS(g_aSupNtImpDlls); iDll++) + for (uint32_t i = 0; i < g_aSupNtImpDlls[iDll].cImports; i++) + { + const char *pszForwarder = supR3HardenedResolveImport(&g_aSupNtImpDlls[iDll], &g_aSupNtImpDlls[iDll].paImports[i], + false); + if (pszForwarder) + { + const char *pszDot = strchr(pszForwarder, '.'); + size_t cchDllName = pszDot - pszForwarder; + SUPHNTIMPFUNC Tmp = g_aSupNtImpDlls[iDll].paImports[i]; + Tmp.pszName = pszDot + 1; + if (cchDllName == sizeof("ntdll") - 1 && RTStrNICmp(pszForwarder, RT_STR_TUPLE("ntdll")) == 0) + supR3HardenedResolveImport(&g_aSupNtImpDlls[0], &Tmp, false); + else if (cchDllName == sizeof("kernelbase") - 1 && RTStrNICmp(pszForwarder, RT_STR_TUPLE("kernelbase")) == 0) + supR3HardenedResolveImport(&g_aSupNtImpDlls[1], &Tmp, false); + else + SUPHNTIMP_ERROR(false, 18, "supR3HardenedWinInitImports", kSupInitOp_Misc, VERR_MODULE_NOT_FOUND, + "%ls: Failed to resolve forwarder '%s'.", g_aSupNtImpDlls[iDll].pwszName, pszForwarder); + } + } + + /* + * Do system calls directly. + */ + supR3HardenedWinInitSyscalls(false, RTErrInfoInitStatic(&ErrInfo)); + + /* + * Use the on disk image to avoid export table patching. Currently + * ignoring errors here as can live normally without this step. + */ + for (uint32_t iDll = 0; iDll < RT_ELEMENTS(g_aSupNtImpDlls); iDll++) + if (g_aSupNtImpDlls[iDll].cPatchedExports > 0) + { + PSUPHNTLDRCACHEENTRY pLdrEntry; + int rc = supHardNtLdrCacheOpen(g_aSupNtImpDlls[iDll].pszName, &pLdrEntry, RTErrInfoInitStatic(&ErrInfo)); + if (RT_SUCCESS(rc)) + { + uint8_t *pbBits; + rc = supHardNtLdrCacheEntryGetBits(pLdrEntry, &pbBits, (uintptr_t)g_aSupNtImpDlls[iDll].pbImageBase, NULL, NULL, + RTErrInfoInitStatic(&ErrInfo)); + if (RT_SUCCESS(rc)) + for (uint32_t i = 0; i < g_aSupNtImpDlls[iDll].cImports; i++) + { + RTLDRADDR uValue; + rc = RTLdrGetSymbolEx(pLdrEntry->hLdrMod, pbBits, (uintptr_t)g_aSupNtImpDlls[iDll].pbImageBase, + UINT32_MAX, g_aSupNtImpDlls[iDll].paImports[i].pszName, &uValue); + if (RT_SUCCESS(rc)) + *g_aSupNtImpDlls[iDll].paImports[i].ppfnImport = (PFNRT)(uintptr_t)uValue; + } + } + } + + +#if 0 /* Win7/32 ntdll!LdrpDebugFlags. */ + *(uint8_t *)&g_aSupNtImpDlls[0].pbImageBase[0xdd770] = 0x3; +#endif +} + + +/** + * Gets the address of a procedure in a DLL, ignoring our own syscall + * implementations. + * + * Currently restricted to NTDLL and KERNEL32 + * + * @returns The procedure address. + * @param pszDll The DLL name. + * @param pszProcedure The procedure name. + */ +DECLHIDDEN(PFNRT) supR3HardenedWinGetRealDllSymbol(const char *pszDll, const char *pszProcedure) +{ + RTERRINFOSTATIC ErrInfo; + + /* + * Look the DLL up in the import DLL table. + */ + for (uint32_t iDll = 0; iDll < RT_ELEMENTS(g_aSupNtImpDlls); iDll++) + if (RTStrICmp(g_aSupNtImpDlls[iDll].pszName, pszDll) == 0) + { + + PSUPHNTLDRCACHEENTRY pLdrEntry; + int rc = supHardNtLdrCacheOpen(g_aSupNtImpDlls[iDll].pszName, &pLdrEntry, RTErrInfoInitStatic(&ErrInfo)); + if (RT_SUCCESS(rc)) + { + uint8_t *pbBits; + rc = supHardNtLdrCacheEntryGetBits(pLdrEntry, &pbBits, (uintptr_t)g_aSupNtImpDlls[iDll].pbImageBase, NULL, NULL, + RTErrInfoInitStatic(&ErrInfo)); + if (RT_SUCCESS(rc)) + { + RTLDRADDR uValue; + rc = RTLdrGetSymbolEx(pLdrEntry->hLdrMod, pbBits, (uintptr_t)g_aSupNtImpDlls[iDll].pbImageBase, + UINT32_MAX, pszProcedure, &uValue); + if (RT_SUCCESS(rc)) + return (PFNRT)(uintptr_t)uValue; + SUP_DPRINTF(("supR3HardenedWinGetRealDllSymbol: Error getting %s in %s -> %Rrc\n", pszProcedure, pszDll, rc)); + } + else + SUP_DPRINTF(("supR3HardenedWinGetRealDllSymbol: supHardNtLdrCacheEntryAllocBits failed on %s: %Rrc %s\n", + pszDll, rc, ErrInfo.Core.pszMsg)); + } + else + SUP_DPRINTF(("supR3HardenedWinGetRealDllSymbol: supHardNtLdrCacheOpen failed on %s: %Rrc %s\n", + pszDll, rc, ErrInfo.Core.pszMsg)); + + /* Complications, just call GetProcAddress. */ + if (g_enmSupR3HardenedMainState >= SUPR3HARDENEDMAINSTATE_WIN_IMPORTS_RESOLVED) + return (PFNRT)GetProcAddress(GetModuleHandleW(g_aSupNtImpDlls[iDll].pwszName), pszProcedure); + return NULL; + } + + supR3HardenedFatal("supR3HardenedWinGetRealDllSymbol: Unknown DLL %s (proc: %s)\n", pszDll, pszProcedure); + /* not reached */ +} + diff --git a/src/VBox/HostDrivers/Support/win/SUPR3HardenedNoCrt-win.cpp b/src/VBox/HostDrivers/Support/win/SUPR3HardenedNoCrt-win.cpp new file mode 100644 index 00000000..711c6077 --- /dev/null +++ b/src/VBox/HostDrivers/Support/win/SUPR3HardenedNoCrt-win.cpp @@ -0,0 +1,501 @@ +/* $Id: SUPR3HardenedNoCrt-win.cpp $ */ +/** @file + * VirtualBox Support Library - Hardened main(), windows bits. + */ + +/* + * Copyright (C) 2006-2023 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 <https://www.gnu.org/licenses>. + * + * 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 * +*********************************************************************************************************************************/ +#include <iprt/nt/nt-and-windows.h> +#include <AccCtrl.h> +#include <AclApi.h> +#ifndef PROCESS_SET_LIMITED_INFORMATION +# define PROCESS_SET_LIMITED_INFORMATION 0x2000 +#endif + +#include <VBox/sup.h> +#include <iprt/errcore.h> +#include <iprt/assert.h> +#include <iprt/ctype.h> +#include <iprt/heap.h> +#include <iprt/string.h> +#include <iprt/initterm.h> +#include <iprt/param.h> +#include <iprt/path.h> +#include <iprt/mem.h> +#include <iprt/utf16.h> + +#include "SUPLibInternal.h" +#include "win/SUPHardenedVerify-win.h" + + +/* + * assert.cpp + */ + +RTDATADECL(char) g_szRTAssertMsg1[1024]; +RTDATADECL(char) g_szRTAssertMsg2[4096]; +RTDATADECL(const char * volatile) g_pszRTAssertExpr; +RTDATADECL(const char * volatile) g_pszRTAssertFile; +RTDATADECL(uint32_t volatile) g_u32RTAssertLine; +RTDATADECL(const char * volatile) g_pszRTAssertFunction; + + +RTDECL(bool) RTAssertMayPanic(void) +{ + return true; +} + + +RTDECL(void) RTAssertMsg1(const char *pszExpr, unsigned uLine, const char *pszFile, const char *pszFunction) +{ + /* + * Fill in the globals. + */ + g_pszRTAssertExpr = pszExpr; + g_pszRTAssertFile = pszFile; + g_pszRTAssertFunction = pszFunction; + g_u32RTAssertLine = uLine; + RTStrPrintf(g_szRTAssertMsg1, sizeof(g_szRTAssertMsg1), + "\n!!Assertion Failed!!\n" + "Expression: %s\n" + "Location : %s(%d) %s\n", + pszExpr, pszFile, uLine, pszFunction); +} + + +RTDECL(void) RTAssertMsg2V(const char *pszFormat, va_list va) +{ + RTStrPrintfV(g_szRTAssertMsg2, sizeof(g_szRTAssertMsg2), pszFormat, va); + if (g_enmSupR3HardenedMainState < SUPR3HARDENEDMAINSTATE_CALLED_TRUSTED_MAIN) + supR3HardenedFatalMsg(g_pszRTAssertExpr, kSupInitOp_Misc, VERR_INTERNAL_ERROR, + "%s%s", g_szRTAssertMsg1, g_szRTAssertMsg2); + else + supR3HardenedError(VERR_INTERNAL_ERROR, false/*fFatal*/, "%s%s", g_szRTAssertMsg1, g_szRTAssertMsg2); +} + + +/* + * Memory allocator. + */ + +/** The handle of the heap we're using. */ +static HANDLE g_hSupR3HardenedHeap = NULL; +/** Number of heaps used during early process init. */ +static uint32_t g_cSupR3HardenedEarlyHeaps = 0; +/** Early process init heaps. */ +static struct +{ + /** The heap handle. */ + RTHEAPSIMPLE hHeap; + /** The heap block pointer. */ + void *pvBlock; + /** The size of the heap block. */ + size_t cbBlock; + /** Number of active allocations on this heap. */ + size_t cAllocations; +} g_aSupR3HardenedEarlyHeaps[8]; + + +static uint32_t supR3HardenedEarlyFind(void *pv) RT_NOTHROW_DEF +{ + uint32_t iHeap = g_cSupR3HardenedEarlyHeaps; + while (iHeap-- > 0) + if ((uintptr_t)pv - (uintptr_t)g_aSupR3HardenedEarlyHeaps[iHeap].pvBlock < g_aSupR3HardenedEarlyHeaps[iHeap].cbBlock) + return iHeap; + return UINT32_MAX; +} + + +static void supR3HardenedEarlyCompact(void) RT_NOTHROW_DEF +{ + uint32_t iHeap = g_cSupR3HardenedEarlyHeaps; + while (iHeap-- > 0) + if (g_aSupR3HardenedEarlyHeaps[iHeap].cAllocations == 0) + { + PVOID pvMem = g_aSupR3HardenedEarlyHeaps[iHeap].pvBlock; + SIZE_T cbMem = g_aSupR3HardenedEarlyHeaps[iHeap].cbBlock; + if (iHeap + 1 < g_cSupR3HardenedEarlyHeaps) + g_aSupR3HardenedEarlyHeaps[iHeap] = g_aSupR3HardenedEarlyHeaps[g_cSupR3HardenedEarlyHeaps - 1]; + g_cSupR3HardenedEarlyHeaps--; + + NTSTATUS rcNt = NtFreeVirtualMemory(NtCurrentProcess(), &pvMem, &cbMem, MEM_RELEASE); + Assert(NT_SUCCESS(rcNt)); RT_NOREF_PV(rcNt); + SUP_DPRINTF(("supR3HardenedEarlyCompact: Removed heap %#u (%#p LB %#zx)\n", iHeap, pvMem, cbMem)); + } +} + + +static void *supR3HardenedEarlyAlloc(size_t cb, bool fZero) RT_NOTHROW_DEF +{ + /* + * Try allocate on existing heaps. + */ + void *pv; + uint32_t iHeap = 0; + while (iHeap < g_cSupR3HardenedEarlyHeaps) + { + if (fZero) + pv = RTHeapSimpleAllocZ(g_aSupR3HardenedEarlyHeaps[iHeap].hHeap, cb, 0); + else + pv = RTHeapSimpleAlloc(g_aSupR3HardenedEarlyHeaps[iHeap].hHeap, cb, 0); + if (pv) + { + g_aSupR3HardenedEarlyHeaps[iHeap].cAllocations++; +#ifdef SUPR3HARDENED_EARLY_HEAP_TRACE + SUP_DPRINTF(("Early heap: %p LB %#zx - alloc\n", pv, cb)); +#endif + return pv; + } + iHeap++; + } + + /* + * Add another heap. + */ + if (iHeap == RT_ELEMENTS(g_aSupR3HardenedEarlyHeaps)) + supR3HardenedFatal("Early heap table is full (cb=%#zx).\n", cb); + SIZE_T cbBlock = iHeap == 0 ? _1M : g_aSupR3HardenedEarlyHeaps[iHeap - 1].cbBlock * 2; + while (cbBlock <= cb * 2) + cbBlock *= 2; + + PVOID pvBlock = NULL; + NTSTATUS rcNt = NtAllocateVirtualMemory(NtCurrentProcess(), &pvBlock, 0 /*ZeroBits*/, &cbBlock, MEM_COMMIT, PAGE_READWRITE); + if (!NT_SUCCESS(rcNt)) + supR3HardenedFatal("NtAllocateVirtualMemory(,,,%#zx,,) failed: rcNt=%#x\n", cbBlock, rcNt); + SUP_DPRINTF(("New simple heap: #%u %p LB %#zx (for %zu allocation)\n", iHeap, pvBlock, cbBlock, cb)); + + RTHEAPSIMPLE hHeap; + int rc = RTHeapSimpleInit(&hHeap, pvBlock, cbBlock); + if (RT_FAILURE(rc)) + supR3HardenedFatal("RTHeapSimpleInit(,%p,%#zx) failed: rc=%#x\n", pvBlock, cbBlock, rc); + + if (fZero) + pv = RTHeapSimpleAllocZ(hHeap, cb, 0); + else + pv = RTHeapSimpleAlloc(hHeap, cb, 0); + if (!pv) + supR3HardenedFatal("RTHeapSimpleAlloc[Z] failed allocating %#zx bytes on a %#zu heap.\n", cb, cbBlock); + + g_aSupR3HardenedEarlyHeaps[iHeap].pvBlock = pvBlock; + g_aSupR3HardenedEarlyHeaps[iHeap].cbBlock = cbBlock; + g_aSupR3HardenedEarlyHeaps[iHeap].cAllocations = 1; + g_aSupR3HardenedEarlyHeaps[iHeap].hHeap = hHeap; + + Assert(g_cSupR3HardenedEarlyHeaps == iHeap); + g_cSupR3HardenedEarlyHeaps = iHeap + 1; + +#ifdef SUPR3HARDENED_EARLY_HEAP_TRACE + SUP_DPRINTF(("Early heap: %p LB %#zx - alloc\n", pv, cb)); +#endif + return pv; +} + + +/** + * Lazy heap initialization function. + * + * @returns Heap handle. + */ +static HANDLE supR3HardenedHeapInit(void) RT_NOTHROW_DEF +{ + Assert(g_enmSupR3HardenedMainState >= SUPR3HARDENEDMAINSTATE_WIN_EP_CALLED); + HANDLE hHeap = RtlCreateHeap(HEAP_GROWABLE | HEAP_CLASS_PRIVATE, NULL /*HeapBase*/, + 0 /*ReserveSize*/, 0 /*CommitSize*/, NULL /*Lock*/, NULL /*Parameters*/); + if (hHeap) + { + g_hSupR3HardenedHeap = hHeap; + return hHeap; + } + + supR3HardenedFatal("RtlCreateHeap failed.\n"); + /* not reached */ +} + + +/** + * Compacts the heaps before enter wait for parent/child. + */ +DECLHIDDEN(void) supR3HardenedWinCompactHeaps(void) +{ + if (g_hSupR3HardenedHeap) + RtlCompactHeap(g_hSupR3HardenedHeap, 0 /*dwFlags*/); + RtlCompactHeap(GetProcessHeap(), 0 /*dwFlags*/); + supR3HardenedEarlyCompact(); +} + + + +#undef RTMemTmpAllocTag +RTDECL(void *) RTMemTmpAllocTag(size_t cb, const char *pszTag) RT_NO_THROW_DEF +{ + return RTMemAllocTag(cb, pszTag); +} + + +#undef RTMemTmpAllocZTag +RTDECL(void *) RTMemTmpAllocZTag(size_t cb, const char *pszTag) RT_NO_THROW_DEF +{ + return RTMemAllocZTag(cb, pszTag); +} + + +#undef RTMemTmpFree +RTDECL(void) RTMemTmpFree(void *pv) RT_NO_THROW_DEF +{ + RTMemFree(pv); +} + + +#undef RTMemAllocTag +RTDECL(void *) RTMemAllocTag(size_t cb, const char *pszTag) RT_NO_THROW_DEF +{ + RT_NOREF1(pszTag); + HANDLE hHeap = g_hSupR3HardenedHeap; + if (!hHeap) + { + if ( g_fSupEarlyProcessInit + && g_enmSupR3HardenedMainState <= SUPR3HARDENEDMAINSTATE_WIN_EP_CALLED) + return supR3HardenedEarlyAlloc(cb, false /*fZero*/); + hHeap = supR3HardenedHeapInit(); + } + + void *pv = RtlAllocateHeap(hHeap, 0 /*fFlags*/, cb); + if (!pv) + supR3HardenedFatal("RtlAllocateHeap failed to allocate %zu bytes.\n", cb); + return pv; +} + + +#undef RTMemAllocZTag +RTDECL(void *) RTMemAllocZTag(size_t cb, const char *pszTag) RT_NO_THROW_DEF +{ + RT_NOREF1(pszTag); + HANDLE hHeap = g_hSupR3HardenedHeap; + if (!hHeap) + { + if ( g_fSupEarlyProcessInit + && g_enmSupR3HardenedMainState <= SUPR3HARDENEDMAINSTATE_WIN_EP_CALLED) + return supR3HardenedEarlyAlloc(cb, true /*fZero*/); + hHeap = supR3HardenedHeapInit(); + } + + void *pv = RtlAllocateHeap(hHeap, HEAP_ZERO_MEMORY, cb); + if (!pv) + supR3HardenedFatal("RtlAllocateHeap failed to allocate %zu bytes.\n", cb); + return pv; +} + + +#undef RTMemAllocVarTag +RTDECL(void *) RTMemAllocVarTag(size_t cbUnaligned, const char *pszTag) RT_NO_THROW_DEF +{ + size_t cbAligned; + if (cbUnaligned >= 16) + cbAligned = RT_ALIGN_Z(cbUnaligned, 16); + else + cbAligned = RT_ALIGN_Z(cbUnaligned, sizeof(void *)); + return RTMemAllocTag(cbAligned, pszTag); +} + + +#undef RTMemAllocZVarTag +RTDECL(void *) RTMemAllocZVarTag(size_t cbUnaligned, const char *pszTag) RT_NO_THROW_DEF +{ + size_t cbAligned; + if (cbUnaligned >= 16) + cbAligned = RT_ALIGN_Z(cbUnaligned, 16); + else + cbAligned = RT_ALIGN_Z(cbUnaligned, sizeof(void *)); + return RTMemAllocZTag(cbAligned, pszTag); +} + + +#undef RTMemReallocTag +RTDECL(void *) RTMemReallocTag(void *pvOld, size_t cbNew, const char *pszTag) RT_NO_THROW_DEF +{ + if (!pvOld) + return RTMemAllocZTag(cbNew, pszTag); + + void *pv; + if (g_fSupEarlyProcessInit) + { + uint32_t iHeap = supR3HardenedEarlyFind(pvOld); + if (iHeap != UINT32_MAX) + { +#if 0 /* RTHeapSimpleRealloc is not implemented */ + /* If this is before we can use a regular heap, we try resize + within the simple heap. (There are a lot of array growing in + the ASN.1 code.) */ + if (g_enmSupR3HardenedMainState < SUPR3HARDENEDMAINSTATE_WIN_IMPORTS_RESOLVED) + { + pv = RTHeapSimpleRealloc(g_aSupR3HardenedEarlyHeaps[iHeap].hHeap, pvOld, cbNew, 0); + if (pv) + { +# ifdef SUPR3HARDENED_EARLY_HEAP_TRACE + SUP_DPRINTF(("Early heap: %p LB %#zx, was %p - realloc\n", pvNew, cbNew, pvOld)); +# endif + return pv; + } + } +#endif + + /* Either we can't reallocate it on the same simple heap, or we're + past hardened main and wish to migrate everything over on the + real heap. */ + size_t cbOld = RTHeapSimpleSize(g_aSupR3HardenedEarlyHeaps[iHeap].hHeap, pvOld); + pv = RTMemAllocTag(cbNew, pszTag); + if (pv) + { + memcpy(pv, pvOld, RT_MIN(cbOld, cbNew)); + RTHeapSimpleFree(g_aSupR3HardenedEarlyHeaps[iHeap].hHeap, pvOld); + if (g_aSupR3HardenedEarlyHeaps[iHeap].cAllocations) + g_aSupR3HardenedEarlyHeaps[iHeap].cAllocations--; + if ( !g_aSupR3HardenedEarlyHeaps[iHeap].cAllocations + && g_enmSupR3HardenedMainState >= SUPR3HARDENEDMAINSTATE_WIN_IMPORTS_RESOLVED) + supR3HardenedEarlyCompact(); + } +# ifdef SUPR3HARDENED_EARLY_HEAP_TRACE + SUP_DPRINTF(("Early heap: %p LB %#zx, was %p %LB %#zx - realloc\n", pv, cbNew, pvOld, cbOld)); +# endif + return pv; + } + Assert(g_enmSupR3HardenedMainState >= SUPR3HARDENEDMAINSTATE_WIN_IMPORTS_RESOLVED); + } + + /* Allocate from the regular heap. */ + HANDLE hHeap = g_hSupR3HardenedHeap; + Assert(hHeap != NULL); + pv = RtlReAllocateHeap(hHeap, 0 /*dwFlags*/, pvOld, cbNew); + if (!pv) + supR3HardenedFatal("RtlReAllocateHeap failed to allocate %zu bytes.\n", cbNew); + return pv; +} + + +#undef RTMemFree +RTDECL(void) RTMemFree(void *pv) RT_NO_THROW_DEF +{ + if (pv) + { + if (g_fSupEarlyProcessInit) + { + uint32_t iHeap = supR3HardenedEarlyFind(pv); + if (iHeap != UINT32_MAX) + { +#ifdef SUPR3HARDENED_EARLY_HEAP_TRACE + SUP_DPRINTF(("Early heap: %p - free\n", pv)); +#endif + RTHeapSimpleFree(g_aSupR3HardenedEarlyHeaps[iHeap].hHeap, pv); + if (g_aSupR3HardenedEarlyHeaps[iHeap].cAllocations) + g_aSupR3HardenedEarlyHeaps[iHeap].cAllocations--; + if ( !g_aSupR3HardenedEarlyHeaps[iHeap].cAllocations + && g_enmSupR3HardenedMainState >= SUPR3HARDENEDMAINSTATE_WIN_IMPORTS_RESOLVED) + supR3HardenedEarlyCompact(); + return; + } + Assert(g_enmSupR3HardenedMainState >= SUPR3HARDENEDMAINSTATE_WIN_IMPORTS_RESOLVED); + } + + HANDLE hHeap = g_hSupR3HardenedHeap; + Assert(hHeap != NULL); + RtlFreeHeap(hHeap, 0 /* dwFlags*/, pv); + } +} + + +/* + * Simplified version of RTMemWipeThoroughly that avoids dragging in the + * random number code. + */ + +RTDECL(void) RTMemWipeThoroughly(void *pv, size_t cb, size_t cMinPasses) RT_NO_THROW_DEF +{ + size_t cPasses = RT_MIN(cMinPasses, 6); + static const uint32_t s_aPatterns[] = { 0x00, 0xaa, 0x55, 0xff, 0xf0, 0x0f, 0xcc, 0x3c, 0xc3 }; + uint32_t iPattern = 0; + do + { + memset(pv, s_aPatterns[iPattern], cb); + iPattern = (iPattern + 1) % RT_ELEMENTS(s_aPatterns); + ASMMemoryFence(); + + memset(pv, s_aPatterns[iPattern], cb); + iPattern = (iPattern + 1) % RT_ELEMENTS(s_aPatterns); + ASMMemoryFence(); + + memset(pv, s_aPatterns[iPattern], cb); + iPattern = (iPattern + 1) % RT_ELEMENTS(s_aPatterns); + ASMMemoryFence(); + } while (cPasses-- > 0); + + memset(pv, 0xff, cb); + ASMMemoryFence(); +} + + + +/* + * path-win.cpp + */ + +RTDECL(int) RTPathGetCurrent(char *pszPath, size_t cbPath) +{ + int rc; + if (g_enmSupR3HardenedMainState < SUPR3HARDENEDMAINSTATE_WIN_IMPORTS_RESOLVED) +/** @todo Rainy day: improve this by checking the process parameter block + * (needs to be normalized). */ + rc = RTStrCopy(pszPath, cbPath, "C:\\"); + else + { + /* + * GetCurrentDirectory may in some cases omit the drive letter, according + * to MSDN, thus the GetFullPathName call. + */ + RTUTF16 wszCurPath[RTPATH_MAX]; + if (GetCurrentDirectoryW(RTPATH_MAX, wszCurPath)) + { + RTUTF16 wszFullPath[RTPATH_MAX]; + if (GetFullPathNameW(wszCurPath, RTPATH_MAX, wszFullPath, NULL)) + rc = RTUtf16ToUtf8Ex(&wszFullPath[0], RTSTR_MAX, &pszPath, cbPath, NULL); + else + rc = RTErrConvertFromWin32(RtlGetLastWin32Error()); + } + else + rc = RTErrConvertFromWin32(RtlGetLastWin32Error()); + } + return rc; +} + diff --git a/src/VBox/HostDrivers/Support/win/SUPSvc-win.cpp b/src/VBox/HostDrivers/Support/win/SUPSvc-win.cpp new file mode 100644 index 00000000..8eb05451 --- /dev/null +++ b/src/VBox/HostDrivers/Support/win/SUPSvc-win.cpp @@ -0,0 +1,908 @@ +/* $Id: SUPSvc-win.cpp $ */ +/** @file + * VirtualBox Support Service - Windows Specific Code. + */ + +/* + * Copyright (C) 2008-2023 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 <https://www.gnu.org/licenses>. + * + * 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 * +*********************************************************************************************************************************/ +#define LOG_GROUP LOG_GROUP_SUP +#include <iprt/win/windows.h> + +#include <VBox/log.h> +#include <VBox/version.h> +#include <iprt/string.h> +#include <iprt/mem.h> +#include <iprt/initterm.h> +#include <iprt/stream.h> +#include <iprt/getopt.h> +#include <iprt/semaphore.h> +#ifdef DEBUG_bird +# include <iprt/env.h> +#endif + +#include "../SUPSvcInternal.h" + + +/********************************************************************************************************************************* +* Defined Constants And Macros * +*********************************************************************************************************************************/ +/** The service name. */ +#define SUPSVC_SERVICE_NAME "VBoxSupSvc" +/** The service display name. */ +#define SUPSVC_SERVICE_DISPLAY_NAME "VirtualBox Support Service" + + +/********************************************************************************************************************************* +* Global Variables * +*********************************************************************************************************************************/ +/** The service control handler handle. */ +static SERVICE_STATUS_HANDLE g_hSupSvcWinCtrlHandler = NULL; +/** The service status. */ +static uint32_t volatile g_u32SupSvcWinStatus = SERVICE_STOPPED; +/** The semaphore the main service thread is waiting on in supSvcWinServiceMain. */ +static RTSEMEVENTMULTI g_hSupSvcWinEvent = NIL_RTSEMEVENTMULTI; + + +/********************************************************************************************************************************* +* Internal Functions * +*********************************************************************************************************************************/ +static SC_HANDLE supSvcWinOpenSCManager(const char *pszAction, DWORD dwAccess); + + +/** + * Opens the service control manager. + * + * When this fails, an error message will be displayed. + * + * @returns Valid handle on success. + * NULL on failure, will display an error message. + * + * @param pszAction The action which is requesting access to SCM. + * @param dwAccess The desired access. + */ +static SC_HANDLE supSvcWinOpenSCManager(const char *pszAction, DWORD dwAccess) +{ + SC_HANDLE hSCM = OpenSCManager(NULL /* lpMachineName*/, NULL /* lpDatabaseName */, dwAccess); + if (hSCM == NULL) + { + DWORD err = GetLastError(); + switch (err) + { + case ERROR_ACCESS_DENIED: + supSvcDisplayError("%s - OpenSCManager failure: access denied\n", pszAction); + break; + default: + supSvcDisplayError("%s - OpenSCManager failure: %d\n", pszAction, err); + break; + } + } + return hSCM; +} + + +/** + * Opens the service. + * + * Last error is preserved on failure and set to 0 on success. + * + * @returns Valid service handle on success. + * NULL on failure, will display an error message unless it's ignored. + * + * @param pszAction The action which is requesting access to the service. + * @param dwSCMAccess The service control manager access. + * @param dwSVCAccess The desired service access. + * @param cIgnoredErrors The number of ignored errors. + * @param ... Errors codes that should not cause a message to be displayed. + */ +static SC_HANDLE supSvcWinOpenService(const char *pszAction, DWORD dwSCMAccess, DWORD dwSVCAccess, + unsigned cIgnoredErrors, ...) +{ + SC_HANDLE hSCM = supSvcWinOpenSCManager(pszAction, dwSCMAccess); + if (!hSCM) + return NULL; + + SC_HANDLE hSvc = OpenService(hSCM, SUPSVC_SERVICE_NAME, dwSVCAccess); + if (hSvc) + { + CloseServiceHandle(hSCM); + SetLastError(0); + } + else + { + DWORD err = GetLastError(); + bool fIgnored = false; + va_list va; + va_start(va, cIgnoredErrors); + while (!fIgnored && cIgnoredErrors-- > 0) + fIgnored = va_arg(va, long) == err; + va_end(va); + if (!fIgnored) + { + switch (err) + { + case ERROR_ACCESS_DENIED: + supSvcDisplayError("%s - OpenService failure: access denied\n", pszAction); + break; + case ERROR_SERVICE_DOES_NOT_EXIST: + supSvcDisplayError("%s - OpenService failure: The service does not exist. Reinstall it.\n", pszAction); + break; + default: + supSvcDisplayError("%s - OpenService failure: %d\n", pszAction, err); + break; + } + } + + CloseServiceHandle(hSCM); + SetLastError(err); + } + return hSvc; +} + + + +void supSvcOsLogErrorStr(const char *pszMsg) +{ + HANDLE hEventLog = RegisterEventSource(NULL /* local computer */, "VBoxSupSvc"); + AssertReturnVoid(hEventLog != NULL); + const char *apsz[2]; + apsz[0] = "VBoxSupSvc"; + apsz[1] = pszMsg; + BOOL fRc = ReportEvent(hEventLog, /* hEventLog */ + EVENTLOG_ERROR_TYPE, /* wType */ + 0, /* wCategory */ + 0 /** @todo mc */, /* dwEventID */ + NULL, /* lpUserSid */ + RT_ELEMENTS(apsz), /* wNumStrings */ + 0, /* dwDataSize */ + apsz, /* lpStrings */ + NULL); /* lpRawData */ + AssertMsg(fRc, ("%d\n", GetLastError())); + DeregisterEventSource(hEventLog); +} + + +static int supSvcWinInterrogate(int argc, char **argv) +{ + RTPrintf("VBoxSupSvc: The \"interrogate\" action is not implemented.\n"); + return 1; +} + + +static int supSvcWinStop(int argc, char **argv) +{ + RTPrintf("VBoxSupSvc: The \"stop\" action is not implemented.\n"); + return 1; +} + + +static int supSvcWinContinue(int argc, char **argv) +{ + RTPrintf("VBoxSupSvc: The \"continue\" action is not implemented.\n"); + return 1; +} + + +static int supSvcWinPause(int argc, char **argv) +{ + RTPrintf("VBoxSupSvc: The \"pause\" action is not implemented.\n"); + return 1; +} + + +static int supSvcWinStart(int argc, char **argv) +{ + RTPrintf("VBoxSupSvc: The \"start\" action is not implemented.\n"); + return 1; +} + + +static int supSvcWinQueryDescription(int argc, char **argv) +{ + RTPrintf("VBoxSupSvc: The \"qdescription\" action is not implemented.\n"); + return 1; +} + + +static int supSvcWinQueryConfig(int argc, char **argv) +{ + RTPrintf("VBoxSupSvc: The \"qconfig\" action is not implemented.\n"); + return 1; +} + + +static int supSvcWinDisable(int argc, char **argv) +{ + RTPrintf("VBoxSupSvc: The \"disable\" action is not implemented.\n"); + return 1; +} + +static int supSvcWinEnable(int argc, char **argv) +{ + RTPrintf("VBoxSupSvc: The \"enable\" action is not implemented.\n"); + return 1; +} + + +/** + * Handle the 'delete' action. + * + * @returns 0 or 1. + * @param argc The action argument count. + * @param argv The action argument vector. + */ +static int supSvcWinDelete(int argc, char **argv) +{ + /* + * Parse the arguments. + */ + bool fVerbose = false; + static const RTGETOPTDEF s_aOptions[] = + { + { "--verbose", 'v', RTGETOPT_REQ_NOTHING } + }; + int ch; + RTGETOPTUNION Value; + RTGETOPTSTATE GetState; + RTGetOptInit(&GetState, argc, argv, s_aOptions, RT_ELEMENTS(s_aOptions), 0, RTGETOPTINIT_FLAGS_NO_STD_OPTS); + while ((ch = RTGetOpt(&GetState, &Value))) + switch (ch) + { + case 'v': + fVerbose = true; + break; + case VINF_GETOPT_NOT_OPTION: + return supSvcDisplayTooManyArgsError("delete", argc, argv, iArg); + default: + return supSvcDisplayGetOptError("delete", ch, argc, argv, iArg, &Value); + } + + /* + * Create the service. + */ + int rc = 1; + SC_HANDLE hSvc = supSvcWinOpenService("delete", SERVICE_CHANGE_CONFIG, DELETE, + 1, ERROR_SERVICE_DOES_NOT_EXIST); + if (hSvc) + { + if (DeleteService(hSvc)) + { + RTPrintf("Successfully deleted the %s service.\n", SUPSVC_SERVICE_NAME); + rc = 0; + } + else + supSvcDisplayError("delete - DeleteService failed, err=%d.\n", GetLastError()); + CloseServiceHandle(hSvc); + } + else if (GetLastError() == ERROR_SERVICE_DOES_NOT_EXIST) + { + + if (fVerbose) + RTPrintf("The service %s was not installed, nothing to be done.", SUPSVC_SERVICE_NAME); + else + RTPrintf("Successfully deleted the %s service.\n", SUPSVC_SERVICE_NAME); + rc = 0; + } + return rc; +} + + +/** + * Handle the 'create' action. + * + * @returns 0 or 1. + * @param argc The action argument count. + * @param argv The action argument vector. + */ +static int supSvcWinCreate(int argc, char **argv) +{ + /* + * Parse the arguments. + */ + bool fVerbose = false; + static const RTOPTIONDEF s_aOptions[] = + { + { "--verbose", 'v', RTGETOPT_REQ_NOTHING } + }; + int iArg = 0; + int ch; + RTGETOPTUNION Value; + while ((ch = RTGetOpt(argc, argv, s_aOptions, RT_ELEMENTS(s_aOptions), &iArg, &Value))) + switch (ch) + { + case 'v': fVerbose = true; break; + default: return supSvcDisplayGetOptError("create", ch, argc, argv, iArg, &Value); + } + if (iArg != argc) + return supSvcDisplayTooManyArgsError("create", argc, argv, iArg); + + /* + * Create the service. + */ + int rc = 1; + SC_HANDLE hSCM = supSvcWinOpenSCManager("create", SC_MANAGER_CREATE_SERVICE); /*SC_MANAGER_ALL_ACCESS*/ + if (hSCM) + { + char szExecPath[MAX_PATH]; + if (GetModuleFileName(NULL /* the executable */, szExecPath, sizeof(szExecPath))) + { + if (fVerbose) + RTPrintf("Creating the %s service, binary \"%s\"...\n", + SUPSVC_SERVICE_NAME, szExecPath); /* yea, the binary name isn't UTF-8, but wtf. */ + + SC_HANDLE hSvc = CreateService(hSCM, /* hSCManager */ + SUPSVC_SERVICE_NAME, /* lpServiceName */ + SUPSVC_SERVICE_DISPLAY_NAME, /* lpDisplayName */ + SERVICE_CHANGE_CONFIG | SERVICE_QUERY_STATUS | SERVICE_QUERY_CONFIG, /* dwDesiredAccess */ + SERVICE_WIN32_OWN_PROCESS, /* dwServiceType ( | SERVICE_INTERACTIVE_PROCESS? ) */ + SERVICE_DEMAND_START/*_AUTO*/, /* dwStartType */ + SERVICE_ERROR_NORMAL, /* dwErrorControl */ + szExecPath, /* lpBinaryPathName */ + NULL, /* lpLoadOrderGroup */ + NULL, /* lpdwTagId */ + NULL, /* lpDependencies */ + NULL, /* lpServiceStartName (=> LocalSystem) */ + NULL); /* lpPassword */ + if (hSvc) + { + RTPrintf("Successfully created the %s service.\n", SUPSVC_SERVICE_NAME); + /** @todo Set the service description or it'll look weird in the vista service manager. + * Anything else that should be configured? Start access or something? */ + rc = 0; + CloseServiceHandle(hSvc); + } + else + { + DWORD err = GetLastError(); + switch (err) + { + case ERROR_SERVICE_EXISTS: + supSvcDisplayError("create - The service already exists.\n"); + break; + default: + supSvcDisplayError("create - CreateService failed, err=%d.\n", GetLastError()); + break; + } + } + CloseServiceHandle(hSvc); + } + else + supSvcDisplayError("create - Failed to obtain the executable path: %d\n", GetLastError()); + } + return rc; +} + + +/** + * Sets the service status, just a SetServiceStatus Wrapper. + * + * @returns See SetServiceStatus. + * @param dwStatus The current status. + * @param iWaitHint The wait hint, if < 0 then supply a default. + * @param dwExitCode The service exit code. + */ +static bool supSvcWinSetServiceStatus(DWORD dwStatus, int iWaitHint, DWORD dwExitCode) +{ + SERVICE_STATUS SvcStatus; + SvcStatus.dwServiceType = SERVICE_WIN32_OWN_PROCESS; + SvcStatus.dwWin32ExitCode = dwExitCode; + SvcStatus.dwServiceSpecificExitCode = 0; + SvcStatus.dwWaitHint = iWaitHint >= 0 ? iWaitHint : 3000; + SvcStatus.dwCurrentState = dwStatus; + LogFlow(("supSvcWinSetServiceStatus: %d -> %d\n", g_u32SupSvcWinStatus, dwStatus)); + g_u32SupSvcWinStatus = dwStatus; + switch (dwStatus) + { + case SERVICE_START_PENDING: + SvcStatus.dwControlsAccepted = 0; + break; + default: + SvcStatus.dwControlsAccepted = SERVICE_ACCEPT_STOP; + break; + } + + static DWORD dwCheckPoint = 0; + switch (dwStatus) + { + case SERVICE_RUNNING: + case SERVICE_STOPPED: + SvcStatus.dwCheckPoint = 0; + default: + SvcStatus.dwCheckPoint = ++dwCheckPoint; + break; + } + return SetServiceStatus(g_hSupSvcWinCtrlHandler, &SvcStatus) != FALSE; +} + + +/** + * Service control handler (extended). + * + * @returns Windows status (see HandlerEx). + * @retval NO_ERROR if handled. + * @retval ERROR_CALL_NOT_IMPLEMENTED if not handled. + * + * @param dwControl The control code. + * @param dwEventType Event type. (specific to the control?) + * @param pvEventData Event data, specific to the event. + * @param pvContext The context pointer registered with the handler. + * Currently not used. + */ +static DWORD WINAPI supSvcWinServiceCtrlHandlerEx(DWORD dwControl, DWORD dwEventType, LPVOID pvEventData, LPVOID pvContext) +{ + LogFlow(("supSvcWinServiceCtrlHandlerEx: dwControl=%#x dwEventType=%#x pvEventData=%p\n", + dwControl, dwEventType, pvEventData)); + + switch (dwControl) + { + /* + * Interrogate the service about it's current status. + * MSDN says that this should just return NO_ERROR and does + * not need to set the status again. + */ + case SERVICE_CONTROL_INTERROGATE: + return NO_ERROR; + + /* + * Request to stop the service. + */ + case SERVICE_CONTROL_STOP: + { + /* + * Check if the real services can be stopped and then tell them to stop. + */ + supSvcWinSetServiceStatus(SERVICE_STOP_PENDING, 3000, NO_ERROR); + int rc = supSvcTryStopServices(); + if (RT_SUCCESS(rc)) + { + /* + * Notify the main thread that we're done, it will wait for the + * real services to stop, destroy them, and finally set the windows + * service status to SERVICE_STOPPED and return. + */ + rc = RTSemEventMultiSignal(g_hSupSvcWinEvent); + if (RT_FAILURE(rc)) + supSvcLogError("SERVICE_CONTROL_STOP: RTSemEventMultiSignal failed, %Rrc\n", rc); + } + return NO_ERROR; + } + + case SERVICE_CONTROL_PAUSE: + case SERVICE_CONTROL_CONTINUE: + case SERVICE_CONTROL_SHUTDOWN: + case SERVICE_CONTROL_PARAMCHANGE: + case SERVICE_CONTROL_NETBINDADD: + case SERVICE_CONTROL_NETBINDREMOVE: + case SERVICE_CONTROL_NETBINDENABLE: + case SERVICE_CONTROL_NETBINDDISABLE: + case SERVICE_CONTROL_DEVICEEVENT: + case SERVICE_CONTROL_HARDWAREPROFILECHANGE: + case SERVICE_CONTROL_POWEREVENT: + case SERVICE_CONTROL_SESSIONCHANGE: +#ifdef SERVICE_CONTROL_PRESHUTDOWN /* vista */ + case SERVICE_CONTROL_PRESHUTDOWN: +#endif + default: + return ERROR_CALL_NOT_IMPLEMENTED; + } + + NOREF(dwEventType); + NOREF(pvEventData); + NOREF(pvContext); + return NO_ERROR; +} + + +/** + * Windows Service Main. + * + * This is invoked when the service is started and should not return until + * the service has been stopped. + * + * @param cArgs Argument count. + * @param papszArgs Argument vector. + */ +static VOID WINAPI supSvcWinServiceMain(DWORD cArgs, LPSTR *papszArgs) +{ + LogFlowFuncEnter(); + + /* + * Register the control handler function for the service and report to SCM. + */ + Assert(g_u32SupSvcWinStatus == SERVICE_STOPPED); + g_hSupSvcWinCtrlHandler = RegisterServiceCtrlHandlerEx(SUPSVC_SERVICE_NAME, supSvcWinServiceCtrlHandlerEx, NULL); + if (g_hSupSvcWinCtrlHandler) + { + DWORD err = ERROR_GEN_FAILURE; + if (supSvcWinSetServiceStatus(SERVICE_START_PENDING, 3000, NO_ERROR)) + { + /* + * Parse arguments. + */ + static const RTOPTIONDEF s_aOptions[] = + { + { "--dummy", 'd', RTGETOPT_REQ_NOTHING } + }; + int iArg = 1; /* the first arg is the service name */ + int ch; + int rc = 0; + RTGETOPTUNION Value; + while ( !rc + && (ch = RTGetOpt(cArgs, papszArgs, s_aOptions, RT_ELEMENTS(s_aOptions), &iArg, &Value))) + switch (ch) + { + default: rc = supSvcLogGetOptError("main", ch, cArgs, papszArgs, iArg, &Value); break; + } + if (iArg != cArgs) + rc = supSvcLogTooManyArgsError("main", cArgs, papszArgs, iArg); + if (!rc) + { + /* + * Create the event semaphore we'll be waiting on and + * then instantiate the actual services. + */ + int rc = RTSemEventMultiCreate(&g_hSupSvcWinEvent); + if (RT_SUCCESS(rc)) + { + rc = supSvcCreateAndStartServices(); + if (RT_SUCCESS(rc)) + { + /* + * Update the status and enter the work loop. + * + * The work loop is just a dummy wait here as the services run + * in independent threads. + */ + if (supSvcWinSetServiceStatus(SERVICE_RUNNING, 0, 0)) + { + LogFlow(("supSvcWinServiceMain: calling RTSemEventMultiWait\n")); + rc = RTSemEventMultiWait(g_hSupSvcWinEvent, RT_INDEFINITE_WAIT); + if (RT_SUCCESS(rc)) + { + LogFlow(("supSvcWinServiceMain: woke up\n")); + err = NO_ERROR; + } + else + supSvcLogError("RTSemEventWait failed, rc=%Rrc", rc); + } + else + { + err = GetLastError(); + supSvcLogError("SetServiceStatus failed, err=%d", err); + } + + /* + * Destroy the service instances, stopping them if + * they're still running (weird failure cause). + */ + supSvcStopAndDestroyServices(); + } + + RTSemEventMultiDestroy(g_hSupSvcWinEvent); + g_hSupSvcWinEvent = NIL_RTSEMEVENTMULTI; + } + else + supSvcLogError("RTSemEventMultiCreate failed, rc=%Rrc", rc); + } + /* else: bad args */ + } + else + { + err = GetLastError(); + supSvcLogError("SetServiceStatus failed, err=%d", err); + } + supSvcWinSetServiceStatus(SERVICE_STOPPED, 0, err); + } + else + supSvcLogError("RegisterServiceCtrlHandlerEx failed, err=%d", GetLastError()); + LogFlowFuncLeave(); +} + + +/** + * Handle the 'create' action. + * + * @returns 0 or 1. + * @param argc The action argument count. + * @param argv The action argument vector. + */ +static int supSvcWinRunIt(int argc, char **argv) +{ + LogFlowFuncEnter(); + + /* + * Initialize release logging. + */ + /** @todo release logging of the system-wide service. */ + + /* + * Parse the arguments. + */ + static const RTOPTIONDEF s_aOptions[] = + { + { "--dummy", 'd', RTGETOPT_REQ_NOTHING } + }; + int iArg = 0; + int ch; + RTGETOPTUNION Value; + while ((ch = RTGetOpt(argc, argv, s_aOptions, RT_ELEMENTS(s_aOptions), &iArg, &Value))) + switch (ch) + { + default: return supSvcDisplayGetOptError("runit", ch, argc, argv, iArg, &Value); + } + if (iArg != argc) + return supSvcDisplayTooManyArgsError("runit", argc, argv, iArg); + + /* + * Register the service with the service control manager + * and start dispatching requests from it (all done by the API). + */ + static SERVICE_TABLE_ENTRY const s_aServiceStartTable[] = + { + { SUPSVC_SERVICE_NAME, supSvcWinServiceMain }, + { NULL, NULL} + }; + if (StartServiceCtrlDispatcher(&s_aServiceStartTable[0])) + { + LogFlowFuncLeave(); + return 0; /* told to quit, so quit. */ + } + + DWORD err = GetLastError(); + switch (err) + { + case ERROR_FAILED_SERVICE_CONTROLLER_CONNECT: + supSvcDisplayError("Cannot run a service from the command line. Use the 'start' action to start it the right way.\n"); + break; + default: + supSvcLogError("StartServiceCtrlDispatcher failed, err=%d", err); + break; + } + return 1; +} + + +/** + * Show the version info. + * + * @returns 0. + */ +static int supSvcWinShowVersion(int argc, char **argv) +{ + /* + * Parse the arguments. + */ + bool fBrief = false; + static const RTOPTIONDEF s_aOptions[] = + { + { "--brief", 'b', RTGETOPT_REQ_NOTHING } + }; + int iArg = 0; + int ch; + RTGETOPTUNION Value; + while ((ch = RTGetOpt(argc, argv, s_aOptions, RT_ELEMENTS(s_aOptions), &iArg, &Value))) + switch (ch) + { + case 'b': fBrief = true; break; + default: return supSvcDisplayGetOptError("version", ch, argc, argv, iArg, &Value); + + } + if (iArg != argc) + return supSvcDisplayTooManyArgsError("version", argc, argv, iArg); + + /* + * Do the printing. + */ + if (fBrief) + RTPrintf("%s\n", VBOX_VERSION_STRING); + else + RTPrintf("VirtualBox System Service Version %s\n" + "Copyright (C) 2008-" VBOX_C_YEAR " Oracle and/or its affiliates\n\n", + VBOX_VERSION_STRING); + return 0; +} + + +/** + * Show the usage help screen. + * + * @returns 0. + */ +static int supSvcWinShowHelp(void) +{ + RTPrintf("VirtualBox System Service Version %s\n" + "Copyright (C) 2008-" VBOX_C_YEAR " Oracle and/or its affiliates\n\n", + VBOX_VERSION_STRING); + RTPrintf("Usage:\n" + "\n" + "VBoxSupSvc\n" + " Runs the service.\n" + "VBoxSupSvc <version|-v|--version> [-brief]\n" + " Displays the version.\n" + "VBoxSupSvc <help|-?|-h|--help> [...]\n" + " Displays this help screen.\n" + "\n" + "VBoxSupSvc <install|/RegServer|/i>\n" + " Installs the service.\n" + "VBoxSupSvc <install|delete|/UnregServer|/u>\n" + " Uninstalls the service.\n" + ); + return 0; +} + + +/** + * VBoxSUPSvc main(), Windows edition. + * + * + * @returns 0 on success. + * + * @param argc Number of arguments in argv. + * @param argv Argument vector. + */ +int main(int argc, char **argv) +{ + /* + * Initialize the IPRT first of all. + */ +#ifdef DEBUG_bird + RTEnvSet("VBOX_LOG", "sup=~0"); + RTEnvSet("VBOX_LOG_DEST", "file=E:\\temp\\VBoxSupSvc.log"); + RTEnvSet("VBOX_LOG_FLAGS", "unbuffered thread msprog"); +#endif + int rc = RTR3InitExe(argc, &argv, 0); + if (RT_FAILURE(rc)) + { + supSvcLogError("RTR3InitExe failed with rc=%Rrc", rc); + return 1; + } + + /* + * Parse the initial arguments to determine the desired action. + */ + enum + { + kSupSvcAction_RunIt, + + kSupSvcAction_Create, + kSupSvcAction_Delete, + + kSupSvcAction_Enable, + kSupSvcAction_Disable, + kSupSvcAction_QueryConfig, + kSupSvcAction_QueryDescription, + + kSupSvcAction_Start, + kSupSvcAction_Pause, + kSupSvcAction_Continue, + kSupSvcAction_Stop, + kSupSvcAction_Interrogate, + + kSupSvcAction_End + } enmAction = kSupSvcAction_RunIt; + int iArg = 1; + if (argc > 1) + { + if ( !stricmp(argv[iArg], "/RegServer") + || !stricmp(argv[iArg], "install") + || !stricmp(argv[iArg], "/i")) + enmAction = kSupSvcAction_Create; + else if ( !stricmp(argv[iArg], "/UnregServer") + || !stricmp(argv[iArg], "/u") + || !stricmp(argv[iArg], "uninstall") + || !stricmp(argv[iArg], "delete")) + enmAction = kSupSvcAction_Delete; + + else if (!stricmp(argv[iArg], "enable")) + enmAction = kSupSvcAction_Enable; + else if (!stricmp(argv[iArg], "disable")) + enmAction = kSupSvcAction_Disable; + else if (!stricmp(argv[iArg], "qconfig")) + enmAction = kSupSvcAction_QueryConfig; + else if (!stricmp(argv[iArg], "qdescription")) + enmAction = kSupSvcAction_QueryDescription; + + else if ( !stricmp(argv[iArg], "start") + || !stricmp(argv[iArg], "/t")) + enmAction = kSupSvcAction_Start; + else if (!stricmp(argv[iArg], "pause")) + enmAction = kSupSvcAction_Start; + else if (!stricmp(argv[iArg], "continue")) + enmAction = kSupSvcAction_Continue; + else if (!stricmp(argv[iArg], "stop")) + enmAction = kSupSvcAction_Stop; + else if (!stricmp(argv[iArg], "interrogate")) + enmAction = kSupSvcAction_Interrogate; + else if ( !stricmp(argv[iArg], "help") + || !stricmp(argv[iArg], "?") + || !stricmp(argv[iArg], "/?") + || !stricmp(argv[iArg], "-?") + || !stricmp(argv[iArg], "/h") + || !stricmp(argv[iArg], "-h") + || !stricmp(argv[iArg], "/help") + || !stricmp(argv[iArg], "-help") + || !stricmp(argv[iArg], "--help")) + return supSvcWinShowHelp(); + else if ( !stricmp(argv[iArg], "version") + || !stricmp(argv[iArg], "/v") + || !stricmp(argv[iArg], "-v") + || !stricmp(argv[iArg], "/version") + || !stricmp(argv[iArg], "-version") + || !stricmp(argv[iArg], "--version")) + return supSvcWinShowVersion(argc - iArg - 1, argv + iArg + 1); + else + iArg--; + iArg++; + } + + /* + * Dispatch it. + */ + switch (enmAction) + { + case kSupSvcAction_RunIt: + return supSvcWinRunIt(argc - iArg, argv + iArg); + + case kSupSvcAction_Create: + return supSvcWinCreate(argc - iArg, argv + iArg); + case kSupSvcAction_Delete: + return supSvcWinDelete(argc - iArg, argv + iArg); + + case kSupSvcAction_Enable: + return supSvcWinEnable(argc - iArg, argv + iArg); + case kSupSvcAction_Disable: + return supSvcWinDisable(argc - iArg, argv + iArg); + case kSupSvcAction_QueryConfig: + return supSvcWinQueryConfig(argc - iArg, argv + iArg); + case kSupSvcAction_QueryDescription: + return supSvcWinQueryDescription(argc - iArg, argv + iArg); + + case kSupSvcAction_Start: + return supSvcWinStart(argc - iArg, argv + iArg); + case kSupSvcAction_Pause: + return supSvcWinPause(argc - iArg, argv + iArg); + case kSupSvcAction_Continue: + return supSvcWinContinue(argc - iArg, argv + iArg); + case kSupSvcAction_Stop: + return supSvcWinStop(argc - iArg, argv + iArg); + case kSupSvcAction_Interrogate: + return supSvcWinInterrogate(argc - iArg, argv + iArg); + + default: + AssertMsgFailed(("enmAction=%d\n", enmAction)); + return 1; + } +} + diff --git a/src/VBox/HostDrivers/Support/win/VBoxDrv.rc b/src/VBox/HostDrivers/Support/win/VBoxDrv.rc new file mode 100644 index 00000000..5600f4db --- /dev/null +++ b/src/VBox/HostDrivers/Support/win/VBoxDrv.rc @@ -0,0 +1,71 @@ +/* $Id: VBoxDrv.rc $ */ +/** @file + * VBoxDrv - Resource file containing version info and icon. + */ + +/* + * Copyright (C) 2006-2023 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 <https://www.gnu.org/licenses>. + * + * 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 + */ + +#include <windows.h> +#include <VBox/version.h> + +LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US + +VS_VERSION_INFO VERSIONINFO + FILEVERSION VBOX_RC_FILE_VERSION + PRODUCTVERSION VBOX_RC_FILE_VERSION + FILEFLAGSMASK VS_FFI_FILEFLAGSMASK + FILEFLAGS VBOX_RC_FILE_FLAGS + FILEOS VBOX_RC_FILE_OS + FILETYPE VBOX_RC_TYPE_DRV + FILESUBTYPE VFT2_DRV_SYSTEM +BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "040904b0" // Lang=US English, CharSet=Unicode + BEGIN + VALUE "FileDescription", "VirtualBox Support Driver\0" + VALUE "InternalName", "VBoxSup\0" + VALUE "OriginalFilename", "VBoxSup.sys\0" + VALUE "CompanyName", VBOX_RC_COMPANY_NAME + VALUE "FileVersion", VBOX_RC_FILE_VERSION_STR + VALUE "LegalCopyright", VBOX_RC_LEGAL_COPYRIGHT + VALUE "ProductName", VBOX_RC_PRODUCT_NAME_STR + VALUE "ProductVersion", VBOX_RC_PRODUCT_VERSION_STR + VBOX_RC_MORE_STRINGS + END + END + BLOCK "VarFileInfo" + BEGIN + VALUE "Translation", 0x409, 1200 + END +END + diff --git a/src/VBox/HostDrivers/Support/win/VBoxSup.inf b/src/VBox/HostDrivers/Support/win/VBoxSup.inf new file mode 100644 index 00000000..2309fb06 --- /dev/null +++ b/src/VBox/HostDrivers/Support/win/VBoxSup.inf @@ -0,0 +1,100 @@ +; $Id: VBoxSup.inf $ +;; @file +; VirtualBox Support Driver - Windows Driver INF file. +; + +; +; Copyright (C) 2006-2023 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 <https://www.gnu.org/licenses>. +; +; 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 +; + +[Version] +Signature="$Windows NT$" +Class=System +ClassGuid={4d36e97d-e325-11ce-bfc1-08002be10318} +Provider=%ORACLE% +;edit-DriverVer=08/26/2008,2.00.0000 +DriverPackageType=KernelService +;cat CatalogFile=VBoxSup.cat + +[DestinationDirs] +DefaultDestDir = 12 + +[DefaultInstall@DOT-NT-ARCH@] +CopyFiles=VBoxSup_CopyFiles + +[DefaultInstall@DOT-NT-ARCH@.Services] +AddService=VBoxSup,0x00000002,VBoxSup_Service + +[DefaultUninstall@DOT-NT-ARCH@] +DefFiles=VBoxSup_CopyFiles +LegacyUninstall=1 + +[DefaultUninstall@DOT-NT-ARCH@.Services] +DelService=VBoxSup,0x00000200 +LegacyUninstall=1 + +;; This does not actually work either (see VBoxUSBMon), though it triggers in the +;; installer rather at manual installation on my test system (could be polluted & confused). +;; This may apparently also causes unloading trouble due to 'root\VBoxSup' or something related to that. +;; [Manufacturer] +;; %ORACLE%=VBoxSup@COMMA-NT-ARCH@ +;; +;; ; Models section (referenced by [Manufacturer]). +;; [VBoxSup@DOT-NT-ARCH@] +;; %VBoxSup.DRVDESC%=VBoxSup_Install,root\VBoxSup +;; +;; [VBoxSup_Install@DOT-NT-ARCH@] +;; CopyFiles=VBoxSup_CopyFiles +;; +;; [VBoxSup_Install@DOT-NT-ARCH@.Services] +;; AddService=VBoxSup,0x00000002,VBoxSup_Service + +[SourceDisksFiles] +VBoxSup.sys=1 + +[SourceDisksNames] +1=%VBoxSup.DSKDESC%, + +[VBoxSup_CopyFiles] +VBoxSup.sys + +[VBoxSup_Service] +DisplayName = %VBoxSup.SVCDESC% +ServiceType = 1 ; SERVICE_KERNEL_DRIVER +;StartType = 3 ; SERVICE_DEMAND_START +StartType = 1 ; autostart to fix Vista problem +ErrorControl = 1 ; SERVICE_ERROR_NORMAL +ServiceBinary = %12%\VBoxSup.sys + +[Strings] +ORACLE = "Oracle Corporation" +VBoxSup.SVCDESC = "VirtualBox Service" +VBoxSup.DRVDESC = "VirtualBox Support Driver" +VBoxSup.DSKDESC = "VirtualBox Support Driver Installation Disk" diff --git a/src/VBox/HostDrivers/Support/win/VBoxSupLib-win.cpp b/src/VBox/HostDrivers/Support/win/VBoxSupLib-win.cpp new file mode 100644 index 00000000..f50cf9c5 --- /dev/null +++ b/src/VBox/HostDrivers/Support/win/VBoxSupLib-win.cpp @@ -0,0 +1,108 @@ +/* $Id: VBoxSupLib-win.cpp $ */ +/** @file + * IPRT - VBoxSupLib.dll, Windows. + */ + +/* + * Copyright (C) 2006-2023 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 <https://www.gnu.org/licenses>. + * + * 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 * +*********************************************************************************************************************************/ +#include <iprt/nt/nt-and-windows.h> + +#include <iprt/path.h> + + +/** + * The Dll main entry point. + * @remarks The dllexport is for forcing the linker to generate an import + * library, so the build system doesn't get confused. + */ +extern "C" __declspec(dllexport) +BOOL __stdcall DllMainEntrypoint(HANDLE hModule, DWORD dwReason, PVOID pvReserved) +{ + RT_NOREF1(pvReserved); + + switch (dwReason) + { + /* + * Make sure the DLL isn't ever unloaded. + */ + case DLL_PROCESS_ATTACH: + { + WCHAR wszName[RTPATH_MAX]; + SetLastError(NO_ERROR); + if ( GetModuleFileNameW((HMODULE)hModule, wszName, RT_ELEMENTS(wszName)) > 0 + && RtlGetLastWin32Error() == NO_ERROR) + { + int cExtraLoads = 2; + while (cExtraLoads-- > 0) + LoadLibraryW(wszName); + } + break; + } + + case DLL_THREAD_ATTACH: + { +#ifdef VBOX_WITH_HARDENING +# ifndef VBOX_WITHOUT_DEBUGGER_CHECKS + /* + * Anti debugging hack that prevents most debug notifications from + * ending up in the debugger. + */ + NTSTATUS rcNt = NtSetInformationThread(GetCurrentThread(), ThreadHideFromDebugger, NULL, 0); + if (!NT_SUCCESS(rcNt)) + { + __debugbreak(); + return FALSE; + } +# endif +#endif + break; + } + + case DLL_THREAD_DETACH: + /* Nothing to do. */ + break; + + case DLL_PROCESS_DETACH: + /* Nothing to do. */ + break; + + default: + /* ignore */ + break; + } + return TRUE; +} + diff --git a/src/VBox/HostDrivers/Support/win/VBoxSupLib.rc b/src/VBox/HostDrivers/Support/win/VBoxSupLib.rc new file mode 100644 index 00000000..6e3d3525 --- /dev/null +++ b/src/VBox/HostDrivers/Support/win/VBoxSupLib.rc @@ -0,0 +1,70 @@ +/* $Id: VBoxSupLib.rc $ */ +/** @file + * VBoxSupLib - Resource file containing version info and icon. + */ + +/* + * Copyright (C) 2015-2023 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 <https://www.gnu.org/licenses>. + * + * 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 + */ + +#include <windows.h> +#include <VBox/version.h> + +LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US + +VS_VERSION_INFO VERSIONINFO + FILEVERSION VBOX_RC_FILE_VERSION + PRODUCTVERSION VBOX_RC_FILE_VERSION + FILEFLAGSMASK VS_FFI_FILEFLAGSMASK + FILEFLAGS VBOX_RC_FILE_FLAGS + FILEOS VBOX_RC_FILE_OS + FILETYPE VBOX_RC_TYPE_DLL + FILESUBTYPE VFT2_UNKNOWN +BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "040904b0" // Lang=US English, CharSet=Unicode + BEGIN + VALUE "FileDescription", "VirtualBox Kernel Support\0" + VALUE "InternalName", "VBoxSupLib\0" + VALUE "OriginalFilename", "VBoxSupLib.dll\0" + VALUE "CompanyName", VBOX_RC_COMPANY_NAME + VALUE "FileVersion", VBOX_RC_FILE_VERSION_STR + VALUE "LegalCopyright", VBOX_RC_LEGAL_COPYRIGHT + VALUE "ProductName", VBOX_RC_PRODUCT_NAME_STR + VALUE "ProductVersion", VBOX_RC_PRODUCT_VERSION_STR + VBOX_RC_MORE_STRINGS + END + END + BLOCK "VarFileInfo" + BEGIN + VALUE "Translation", 0x409, 1200 + END +END diff --git a/src/VBox/HostDrivers/Support/win/import-template-kernel32.h b/src/VBox/HostDrivers/Support/win/import-template-kernel32.h new file mode 100644 index 00000000..f761d734 --- /dev/null +++ b/src/VBox/HostDrivers/Support/win/import-template-kernel32.h @@ -0,0 +1,21 @@ +SUPHARNT_IMPORT_STDCALL(CreateFileW, 28) +SUPHARNT_IMPORT_STDCALL(CreateProcessW, 40) +SUPHARNT_IMPORT_STDCALL(ExitProcess, 4) +SUPHARNT_IMPORT_STDCALL(GetFullPathNameA, 16) +SUPHARNT_IMPORT_STDCALL(GetFullPathNameW, 16) +SUPHARNT_IMPORT_STDCALL(GetCurrentDirectoryW, 8) +SUPHARNT_IMPORT_STDCALL(GetLongPathNameW, 12) +SUPHARNT_IMPORT_STDCALL(GetModuleFileNameW, 12) +SUPHARNT_IMPORT_STDCALL(GetModuleHandleA, 4) +SUPHARNT_IMPORT_STDCALL(GetModuleHandleW, 4) +SUPHARNT_IMPORT_STDCALL(GetProcAddress, 8) +SUPHARNT_IMPORT_STDCALL(GetProcessHeap, 0) +SUPHARNT_IMPORT_STDCALL(GetSystemDirectoryW, 8) +SUPHARNT_IMPORT_STDCALL(GetTickCount, 0) +SUPHARNT_IMPORT_STDCALL(LoadLibraryExW, 12) +SUPHARNT_IMPORT_STDCALL(OutputDebugStringA, 4) +SUPHARNT_IMPORT_STDCALL(TlsAlloc, 0) +SUPHARNT_IMPORT_STDCALL(TlsGetValue, 4) +SUPHARNT_IMPORT_STDCALL(TlsSetValue, 8) +SUPHARNT_IMPORT_STDCALL(WriteFile, 20) + diff --git a/src/VBox/HostDrivers/Support/win/import-template-ntdll.h b/src/VBox/HostDrivers/Support/win/import-template-ntdll.h new file mode 100644 index 00000000..fcd7a24b --- /dev/null +++ b/src/VBox/HostDrivers/Support/win/import-template-ntdll.h @@ -0,0 +1,99 @@ +SUPHARNT_IMPORT_SYSCALL(NtAllocateVirtualMemory, 24) +SUPHARNT_IMPORT_SYSCALL(NtClearEvent, 4) +SUPHARNT_IMPORT_SYSCALL(NtClose, 4) +SUPHARNT_IMPORT_SYSCALL(NtCreateEvent, 20) +SUPHARNT_IMPORT_SYSCALL(NtCreateFile, 44) +SUPHARNT_IMPORT_SYSCALL(NtCreateSymbolicLinkObject, 16) +SUPHARNT_IMPORT_SYSCALL(NtDelayExecution, 8) +SUPHARNT_IMPORT_SYSCALL(NtDeviceIoControlFile, 40) +SUPHARNT_IMPORT_SYSCALL(NtDuplicateObject, 28) +SUPHARNT_IMPORT_SYSCALL(NtFlushBuffersFile, 8) +SUPHARNT_IMPORT_SYSCALL(NtFreeVirtualMemory, 16) +SUPHARNT_IMPORT_SYSCALL(NtGetContextThread, 8) +SUPHARNT_IMPORT_SYSCALL(NtMapViewOfSection, 40) +SUPHARNT_IMPORT_SYSCALL(NtOpenDirectoryObject, 12) +SUPHARNT_IMPORT_SYSCALL(NtOpenEvent, 12) +SUPHARNT_IMPORT_SYSCALL(NtOpenKey, 12) +SUPHARNT_IMPORT_SYSCALL(NtOpenProcess, 16) +SUPHARNT_IMPORT_SYSCALL(NtOpenProcessToken, 12) +SUPHARNT_IMPORT_SYSCALL(NtOpenSymbolicLinkObject, 12) +SUPHARNT_IMPORT_SYSCALL(NtOpenThread, 16) +SUPHARNT_IMPORT_SYSCALL(NtOpenThreadToken, 16) +SUPHARNT_IMPORT_SYSCALL(NtProtectVirtualMemory, 20) +SUPHARNT_IMPORT_SYSCALL(NtQueryDirectoryFile, 44) +SUPHARNT_IMPORT_SYSCALL(NtQueryDirectoryObject, 28) +SUPHARNT_IMPORT_SYSCALL(NtQueryEvent, 20) +SUPHARNT_IMPORT_SYSCALL(NtQueryInformationFile, 20) +SUPHARNT_IMPORT_SYSCALL(NtQueryInformationProcess, 20) +SUPHARNT_IMPORT_SYSCALL(NtQueryInformationThread, 20) +SUPHARNT_IMPORT_SYSCALL(NtQueryInformationToken, 20) +SUPHARNT_IMPORT_SYSCALL(NtQueryObject, 20) +SUPHARNT_IMPORT_SYSCALL(NtQuerySecurityObject, 20) +SUPHARNT_IMPORT_SYSCALL(NtQuerySymbolicLinkObject, 12) +SUPHARNT_IMPORT_SYSCALL(NtQuerySystemInformation, 16) +SUPHARNT_IMPORT_SYSCALL(NtQueryTimerResolution, 12) +SUPHARNT_IMPORT_SYSCALL(NtQueryValueKey, 24) +SUPHARNT_IMPORT_SYSCALL(NtQueryVirtualMemory, 24) +SUPHARNT_IMPORT_SYSCALL(NtReadFile, 36) +SUPHARNT_IMPORT_SYSCALL(NtReadVirtualMemory, 20) +SUPHARNT_IMPORT_SYSCALL(NtResetEvent, 8) +SUPHARNT_IMPORT_SYSCALL(NtResumeProcess, 4) +SUPHARNT_IMPORT_SYSCALL(NtResumeThread, 8) +SUPHARNT_IMPORT_SYSCALL(NtSetContextThread, 8) +SUPHARNT_IMPORT_SYSCALL(NtSetEvent, 8) +SUPHARNT_IMPORT_SYSCALL(NtSetInformationFile, 20) +SUPHARNT_IMPORT_SYSCALL(NtSetInformationObject, 16) +SUPHARNT_IMPORT_SYSCALL(NtSetInformationProcess, 16) +SUPHARNT_IMPORT_SYSCALL(NtSetInformationThread, 16) +SUPHARNT_IMPORT_SYSCALL(NtSetTimerResolution, 12) +SUPHARNT_IMPORT_SYSCALL(NtSuspendProcess, 4) +SUPHARNT_IMPORT_SYSCALL(NtSuspendThread, 8) +SUPHARNT_IMPORT_SYSCALL(NtTerminateProcess, 8) +SUPHARNT_IMPORT_SYSCALL(NtTerminateThread, 8) +SUPHARNT_IMPORT_SYSCALL(NtTestAlert, 0) +SUPHARNT_IMPORT_SYSCALL(NtUnmapViewOfSection, 8) +SUPHARNT_IMPORT_SYSCALL(NtWaitForMultipleObjects, 20) +SUPHARNT_IMPORT_SYSCALL(NtWaitForSingleObject, 12) +SUPHARNT_IMPORT_SYSCALL(NtWriteFile, 36) +SUPHARNT_IMPORT_SYSCALL(NtWriteVirtualMemory, 20) +SUPHARNT_IMPORT_SYSCALL(NtYieldExecution, 0) +SUPHARNT_IMPORT_SYSCALL(NtCreateSection, 28) +SUPHARNT_IMPORT_SYSCALL(NtQueryVolumeInformationFile, 20) + +SUPHARNT_IMPORT_STDCALL_EARLY(LdrInitializeThunk, 12) +SUPHARNT_IMPORT_STDCALL_EARLY_OPTIONAL(LdrRegisterDllNotification, 16) +SUPHARNT_IMPORT_STDCALL_EARLY(LdrGetDllHandle, 16) +SUPHARNT_IMPORT_STDCALL_EARLY_OPTIONAL(ApiSetQueryApiSetPresence, 8) + +SUPHARNT_IMPORT_STDCALL(RtlAddAccessAllowedAce, 16) +SUPHARNT_IMPORT_STDCALL(RtlAddAccessDeniedAce, 16) +SUPHARNT_IMPORT_STDCALL(RtlAllocateHeap, 12) +SUPHARNT_IMPORT_STDCALL(RtlCompactHeap, 8) +SUPHARNT_IMPORT_STDCALL(RtlCopySid, 12) +SUPHARNT_IMPORT_STDCALL(RtlCreateAcl, 12) +SUPHARNT_IMPORT_STDCALL(RtlCreateHeap, 24) +SUPHARNT_IMPORT_STDCALL(RtlCreateProcessParameters, 40) +SUPHARNT_IMPORT_STDCALL(RtlCreateSecurityDescriptor, 8) +SUPHARNT_IMPORT_STDCALL(RtlCreateUserProcess, 40) +SUPHARNT_IMPORT_STDCALL(RtlCreateUserThread, 40) +SUPHARNT_IMPORT_STDCALL(RtlDestroyProcessParameters, 4) +SUPHARNT_IMPORT_STDCALL_EARLY(RtlDosApplyFileIsolationRedirection_Ustr, 36) +SUPHARNT_IMPORT_STDCALL_EARLY(RtlEqualSid, 8) +SUPHARNT_IMPORT_STDCALL_EARLY_OPTIONAL(RtlExitUserProcess, 4) +SUPHARNT_IMPORT_STDCALL_EARLY(RtlExitUserThread, 4) +SUPHARNT_IMPORT_STDCALL(RtlExpandEnvironmentStrings_U, 16) +SUPHARNT_IMPORT_STDCALL(RtlFreeHeap, 12) +SUPHARNT_IMPORT_STDCALL_EARLY(RtlFreeUnicodeString, 4) +SUPHARNT_IMPORT_STDCALL_EARLY(RtlGetLastNtStatus, 0) +SUPHARNT_IMPORT_STDCALL_EARLY(RtlGetLastWin32Error, 0) +SUPHARNT_IMPORT_STDCALL_EARLY(RtlGetVersion, 4) +SUPHARNT_IMPORT_STDCALL_EARLY(RtlInitializeSid, 12) +SUPHARNT_IMPORT_STDCALL_EARLY(RtlNtStatusToDosError, 4) +SUPHARNT_IMPORT_STDCALL_EARLY(RtlReAllocateHeap, 16) +SUPHARNT_IMPORT_STDCALL_EARLY(RtlRestoreLastWin32Error, 4) +SUPHARNT_IMPORT_STDCALL(RtlSetDaclSecurityDescriptor, 16) +SUPHARNT_IMPORT_STDCALL_EARLY(RtlSetLastWin32Error, 4) +SUPHARNT_IMPORT_STDCALL_EARLY(RtlSetLastWin32ErrorAndNtStatusFromNtStatus, 4) +SUPHARNT_IMPORT_STDCALL(RtlSizeHeap, 12) +SUPHARNT_IMPORT_STDCALL_EARLY(RtlSubAuthoritySid, 8) + |