summaryrefslogtreecommitdiffstats
path: root/src/VBox/HostDrivers/Support/win
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-11 08:17:27 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-11 08:17:27 +0000
commitf215e02bf85f68d3a6106c2a1f4f7f063f819064 (patch)
tree6bb5b92c046312c4e95ac2620b10ddf482d3fa8b /src/VBox/HostDrivers/Support/win
parentInitial commit. (diff)
downloadvirtualbox-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 'src/VBox/HostDrivers/Support/win')
-rw-r--r--src/VBox/HostDrivers/Support/win/Makefile.kup0
-rw-r--r--src/VBox/HostDrivers/Support/win/SUPDrv-win.cpp5719
-rw-r--r--src/VBox/HostDrivers/Support/win/SUPDrvA-win.asm119
-rw-r--r--src/VBox/HostDrivers/Support/win/SUPHardenedVerify-win.h303
-rw-r--r--src/VBox/HostDrivers/Support/win/SUPHardenedVerifyImage-win.cpp3090
-rw-r--r--src/VBox/HostDrivers/Support/win/SUPHardenedVerifyProcess-win.cpp2681
-rw-r--r--src/VBox/HostDrivers/Support/win/SUPLib-win.cpp1002
-rw-r--r--src/VBox/HostDrivers/Support/win/SUPR0IdcClient-win.c167
-rw-r--r--src/VBox/HostDrivers/Support/win/SUPR3HardenedMain-win.cpp7031
-rw-r--r--src/VBox/HostDrivers/Support/win/SUPR3HardenedMainA-win.asm404
-rw-r--r--src/VBox/HostDrivers/Support/win/SUPR3HardenedMainImports-win.cpp873
-rw-r--r--src/VBox/HostDrivers/Support/win/SUPR3HardenedNoCrt-win.cpp501
-rw-r--r--src/VBox/HostDrivers/Support/win/SUPSvc-win.cpp908
-rw-r--r--src/VBox/HostDrivers/Support/win/VBoxDrv.rc71
-rw-r--r--src/VBox/HostDrivers/Support/win/VBoxSup.inf100
-rw-r--r--src/VBox/HostDrivers/Support/win/VBoxSupLib-win.cpp108
-rw-r--r--src/VBox/HostDrivers/Support/win/VBoxSupLib.rc70
-rw-r--r--src/VBox/HostDrivers/Support/win/import-template-kernel32.h21
-rw-r--r--src/VBox/HostDrivers/Support/win/import-template-ntdll.h99
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)
+