summaryrefslogtreecommitdiffstats
path: root/src/VBox/HostDrivers/Support/win/SUPR3HardenedMain-win.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/VBox/HostDrivers/Support/win/SUPR3HardenedMain-win.cpp')
-rw-r--r--src/VBox/HostDrivers/Support/win/SUPR3HardenedMain-win.cpp7031
1 files changed, 7031 insertions, 0 deletions
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;
+}
+