From f215e02bf85f68d3a6106c2a1f4f7f063f819064 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Thu, 11 Apr 2024 10:17:27 +0200 Subject: Adding upstream version 7.0.14-dfsg. Signed-off-by: Daniel Baumann --- .../Support/win/SUPR3HardenedMain-win.cpp | 7031 ++++++++++++++++++++ 1 file changed, 7031 insertions(+) create mode 100644 src/VBox/HostDrivers/Support/win/SUPR3HardenedMain-win.cpp (limited to 'src/VBox/HostDrivers/Support/win/SUPR3HardenedMain-win.cpp') 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 . + * + * 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 +#include +#include +#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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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""));*/ + + /* + * 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"")); + 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"")); + } + + 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 + * + * (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; +} + -- cgit v1.2.3