VirtualBox

source: vbox/trunk/src/VBox/HostDrivers/Support/win/SUPR3HardenedMain-win.cpp@ 67981

Last change on this file since 67981 was 67981, checked in by vboxsync, 7 years ago

Windows 7+

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 277.4 KB
RevLine 
[51770]1/* $Id: SUPR3HardenedMain-win.cpp 67981 2017-07-15 13:22:24Z vboxsync $ */
2/** @file
3 * VirtualBox Support Library - Hardened main(), windows bits.
4 */
5
6/*
[62490]7 * Copyright (C) 2006-2016 Oracle Corporation
[51770]8 *
9 * This file is part of VirtualBox Open Source Edition (OSE), as
10 * available from http://www.virtualbox.org. This file is free software;
11 * you can redistribute it and/or modify it under the terms of the GNU
12 * General Public License (GPL) as published by the Free Software
13 * Foundation, in version 2 as it comes in the "COPYING" file of the
14 * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
15 * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
16 *
17 * The contents of this file may alternatively be used under the terms
18 * of the Common Development and Distribution License Version 1.0
19 * (CDDL) only, as it comes in the "COPYING.CDDL" file of the
20 * VirtualBox OSE distribution, in which case the provisions of the
21 * CDDL are applicable instead of those of the GPL.
22 *
23 * You may elect to license modified versions of this file under the
24 * terms and conditions of either the GPL or the CDDL or both.
25 */
26
[57358]27
28/*********************************************************************************************************************************
29* Header Files *
30*********************************************************************************************************************************/
[51770]31#include <iprt/nt/nt-and-windows.h>
32#include <AccCtrl.h>
33#include <AclApi.h>
34#ifndef PROCESS_SET_LIMITED_INFORMATION
35# define PROCESS_SET_LIMITED_INFORMATION 0x2000
36#endif
37#ifndef LOAD_LIBRARY_SEARCH_APPLICATION_DIR
[56733]38# define LOAD_LIBRARY_SEARCH_DLL_LOAD_DIR UINT32_C(0x100)
39# define LOAD_LIBRARY_SEARCH_APPLICATION_DIR UINT32_C(0x200)
40# define LOAD_LIBRARY_SEARCH_USER_DIRS UINT32_C(0x400)
41# define LOAD_LIBRARY_SEARCH_SYSTEM32 UINT32_C(0x800)
[51770]42#endif
43
44#include <VBox/sup.h>
45#include <VBox/err.h>
[52403]46#include <VBox/dis.h>
[51770]47#include <iprt/ctype.h>
48#include <iprt/string.h>
49#include <iprt/initterm.h>
50#include <iprt/param.h>
[52204]51#include <iprt/path.h>
[52632]52#include <iprt/thread.h>
[52139]53#include <iprt/zero.h>
[51770]54
55#include "SUPLibInternal.h"
56#include "win/SUPHardenedVerify-win.h"
[51907]57#include "../SUPDrvIOC.h"
[51770]58
[52204]59#ifndef IMAGE_SCN_TYPE_NOLOAD
60# define IMAGE_SCN_TYPE_NOLOAD 0x00000002
61#endif
[51770]62
[52204]63
[57358]64/*********************************************************************************************************************************
65* Defined Constants And Macros *
66*********************************************************************************************************************************/
[52139]67/** The first argument of a respawed stub when respawned for the first time.
[51770]68 * This just needs to be unique enough to avoid most confusion with real
69 * executable names, there are other checks in place to make sure we've respanwed. */
[52426]70#define SUPR3_RESPAWN_1_ARG0 "60eaff78-4bdd-042d-2e72-669728efd737-suplib-2ndchild"
[51770]71
[52139]72/** The first argument of a respawed stub when respawned for the second time.
73 * This just needs to be unique enough to avoid most confusion with real
74 * executable names, there are other checks in place to make sure we've respanwed. */
[52426]75#define SUPR3_RESPAWN_2_ARG0 "60eaff78-4bdd-042d-2e72-669728efd737-suplib-3rdchild"
[52139]76
[51770]77/** Unconditional assertion. */
78#define SUPR3HARDENED_ASSERT(a_Expr) \
79 do { \
80 if (!(a_Expr)) \
[52632]81 supR3HardenedFatal("%s: %s\n", __FUNCTION__, #a_Expr); \
[51770]82 } while (0)
83
84/** Unconditional assertion of NT_SUCCESS. */
85#define SUPR3HARDENED_ASSERT_NT_SUCCESS(a_Expr) \
86 do { \
87 NTSTATUS rcNtAssert = (a_Expr); \
88 if (!NT_SUCCESS(rcNtAssert)) \
[52632]89 supR3HardenedFatal("%s: %s -> %#x\n", __FUNCTION__, #a_Expr, rcNtAssert); \
[51770]90 } while (0)
91
92/** Unconditional assertion of a WIN32 API returning non-FALSE. */
93#define SUPR3HARDENED_ASSERT_WIN32_SUCCESS(a_Expr) \
94 do { \
95 BOOL fRcAssert = (a_Expr); \
96 if (fRcAssert == FALSE) \
[52940]97 supR3HardenedFatal("%s: %s -> %#x\n", __FUNCTION__, #a_Expr, RtlGetLastWin32Error()); \
[51770]98 } while (0)
99
100
[57358]101/*********************************************************************************************************************************
102* Structures and Typedefs *
103*********************************************************************************************************************************/
[51770]104/**
105 * Security descriptor cleanup structure.
106 */
107typedef struct MYSECURITYCLEANUP
108{
109 union
110 {
111 SID Sid;
112 uint8_t abPadding[SECURITY_MAX_SID_SIZE];
113 } Everyone, Owner, User, Login;
114 union
115 {
116 ACL AclHdr;
117 uint8_t abPadding[1024];
118 } Acl;
119 PSECURITY_DESCRIPTOR pSecDesc;
120} MYSECURITYCLEANUP;
121/** Pointer to security cleanup structure. */
122typedef MYSECURITYCLEANUP *PMYSECURITYCLEANUP;
123
124
125/**
126 * Image verifier cache entry.
127 */
128typedef struct VERIFIERCACHEENTRY
129{
130 /** Pointer to the next entry with the same hash value. */
131 struct VERIFIERCACHEENTRY * volatile pNext;
[52403]132 /** Next entry in the WinVerifyTrust todo list. */
133 struct VERIFIERCACHEENTRY * volatile pNextTodoWvt;
134
[51770]135 /** The file handle. */
136 HANDLE hFile;
137 /** If fIndexNumber is set, this is an file system internal file identifier. */
138 LARGE_INTEGER IndexNumber;
139 /** The path hash value. */
140 uint32_t uHash;
141 /** The verification result. */
142 int rc;
[53045]143 /** Used for shutting up load and error messages after a while so they don't
[64532]144 * flood the log file and fill up the disk. */
[53045]145 uint32_t volatile cHits;
[52431]146 /** The validation flags (for WinVerifyTrust retry). */
147 uint32_t fFlags;
[51770]148 /** Whether IndexNumber is valid */
149 bool fIndexNumberValid;
[52403]150 /** Whether verified by WinVerifyTrust. */
151 bool volatile fWinVerifyTrust;
[51770]152 /** cwcPath * sizeof(RTUTF16). */
153 uint16_t cbPath;
154 /** The full path of this entry (variable size). */
155 RTUTF16 wszPath[1];
156} VERIFIERCACHEENTRY;
157/** Pointer to an image verifier path entry. */
158typedef VERIFIERCACHEENTRY *PVERIFIERCACHEENTRY;
159
160
[52403]161/**
162 * Name of an import DLL that we need to check out.
163 */
164typedef struct VERIFIERCACHEIMPORT
165{
166 /** Pointer to the next DLL in the list. */
167 struct VERIFIERCACHEIMPORT * volatile pNext;
168 /** The length of pwszAltSearchDir if available. */
169 uint32_t cwcAltSearchDir;
170 /** This points the directory containing the DLL needing it, this will be
171 * NULL for a System32 DLL. */
172 PWCHAR pwszAltSearchDir;
173 /** The name of the import DLL (variable length). */
174 char szName[1];
175} VERIFIERCACHEIMPORT;
176/** Pointer to a import DLL that needs checking out. */
177typedef VERIFIERCACHEIMPORT *PVERIFIERCACHEIMPORT;
178
179
[52943]180/**
[52969]181 * Child requests.
[52943]182 */
[52969]183typedef enum SUPR3WINCHILDREQ
184{
185 /** Perform child purification and close full access handles (must be zero). */
186 kSupR3WinChildReq_PurifyChildAndCloseHandles = 0,
187 /** Close the events, we're good on our own from here on. */
188 kSupR3WinChildReq_CloseEvents,
189 /** Reporting error. */
190 kSupR3WinChildReq_Error,
191 /** End of valid requests. */
192 kSupR3WinChildReq_End
193} SUPR3WINCHILDREQ;
194
195/**
196 * Child process parameters.
197 */
[52943]198typedef struct SUPR3WINPROCPARAMS
199{
200 /** The event semaphore the child will be waiting on. */
[52969]201 HANDLE hEvtChild;
[52943]202 /** The event semaphore the parent will be waiting on. */
[52969]203 HANDLE hEvtParent;
[52943]204
[52969]205 /** The address of the NTDLL. This is only valid during the very early
206 * initialization as we abuse for thread creation protection. */
207 uintptr_t uNtDllAddr;
[52943]208
[52969]209 /** The requested operation (set by the child). */
210 SUPR3WINCHILDREQ enmRequest;
[52943]211 /** The last status. */
[52969]212 int32_t rc;
[52962]213 /** The init operation the error relates to if message, kSupInitOp_Invalid if
214 * not message. */
[52969]215 SUPINITOP enmWhat;
[52962]216 /** Where if message. */
[52969]217 char szWhere[80];
[52943]218 /** Error message / path name string space. */
[57501]219 char szErrorMsg[16384+1024];
[52943]220} SUPR3WINPROCPARAMS;
221
222
[52969]223/**
224 * Child process data structure for use during child process init setup and
225 * purification.
226 */
227typedef struct SUPR3HARDNTCHILD
228{
229 /** Process handle. */
230 HANDLE hProcess;
231 /** Primary thread handle. */
232 HANDLE hThread;
233 /** Handle to the parent process, if we're the middle (stub) process. */
234 HANDLE hParent;
235 /** The event semaphore the child will be waiting on. */
236 HANDLE hEvtChild;
237 /** The event semaphore the parent will be waiting on. */
238 HANDLE hEvtParent;
239 /** The address of NTDLL in the child. */
240 uintptr_t uNtDllAddr;
241 /** The address of NTDLL in this process. */
242 uintptr_t uNtDllParentAddr;
243 /** Which respawn number this is (1 = stub, 2 = VM). */
244 int iWhich;
245 /** The basic process info. */
246 PROCESS_BASIC_INFORMATION BasicInfo;
247 /** The probable size of the PEB. */
248 size_t cbPeb;
249 /** The pristine process environment block. */
250 PEB Peb;
251 /** The child process parameters. */
252 SUPR3WINPROCPARAMS ProcParams;
253} SUPR3HARDNTCHILD;
254/** Pointer to a child process data structure. */
255typedef SUPR3HARDNTCHILD *PSUPR3HARDNTCHILD;
256
257
[57358]258/*********************************************************************************************************************************
259* Global Variables *
260*********************************************************************************************************************************/
[52943]261/** Process parameters. Specified by parent if VM process, see
262 * supR3HardenedVmProcessInit. */
[52969]263static SUPR3WINPROCPARAMS g_ProcParams = { NULL, NULL, 0, (SUPR3WINCHILDREQ)0, 0 };
[52949]264/** Set if supR3HardenedEarlyProcessInit was invoked. */
265bool g_fSupEarlyProcessInit = false;
266/** Set if the stub device has been opened (stub process only). */
267bool g_fSupStubOpened = false;
[52943]268
[51770]269/** @name Global variables initialized by suplibHardenedWindowsMain.
270 * @{ */
271/** Combined windows NT version number. See SUP_MAKE_NT_VER_COMBINED. */
272uint32_t g_uNtVerCombined = 0;
273/** Count calls to the special main function for linking santity checks. */
274static uint32_t volatile g_cSuplibHardenedWindowsMainCalls;
275/** The UTF-16 windows path to the executable. */
276RTUTF16 g_wszSupLibHardenedExePath[1024];
277/** The NT path of the executable. */
278SUPSYSROOTDIRBUF g_SupLibHardenedExeNtPath;
[56733]279/** The NT path of the application binary directory. */
280SUPSYSROOTDIRBUF g_SupLibHardenedAppBinNtPath;
[51770]281/** The offset into g_SupLibHardenedExeNtPath of the executable name (WCHAR,
282 * not byte). This also gives the length of the exectuable directory path,
283 * including a trailing slash. */
[56733]284static uint32_t g_offSupLibHardenedExeNtName;
285/** Set if we need to use the LOAD_LIBRARY_SEARCH_USER_DIRS option. */
286bool g_fSupLibHardenedDllSearchUserDirs = false;
[51770]287/** @} */
288
289/** @name Hook related variables.
290 * @{ */
291/** Pointer to the bit of assembly code that will perform the original
292 * NtCreateSection operation. */
293static NTSTATUS (NTAPI * g_pfnNtCreateSectionReal)(PHANDLE, ACCESS_MASK, POBJECT_ATTRIBUTES,
294 PLARGE_INTEGER, ULONG, ULONG, HANDLE);
[52953]295/** Pointer to the NtCreateSection function in NtDll (for patching purposes). */
296static uint8_t *g_pbNtCreateSection;
297/** The patched NtCreateSection bytes (for restoring). */
298static uint8_t g_abNtCreateSectionPatch[16];
[52403]299/** Pointer to the bit of assembly code that will perform the original
300 * LdrLoadDll operation. */
301static NTSTATUS (NTAPI * g_pfnLdrLoadDllReal)(PWSTR, PULONG, PUNICODE_STRING, PHANDLE);
[52953]302/** Pointer to the LdrLoadDll function in NtDll (for patching purposes). */
303static uint8_t *g_pbLdrLoadDll;
304/** The patched LdrLoadDll bytes (for restoring). */
305static uint8_t g_abLdrLoadDllPatch[16];
306
[51770]307/** The hash table of verifier cache . */
[52431]308static PVERIFIERCACHEENTRY volatile g_apVerifierCache[128];
[52403]309/** Queue of cached images which needs WinVerifyTrust to check them. */
[52431]310static PVERIFIERCACHEENTRY volatile g_pVerifierCacheTodoWvt = NULL;
[52403]311/** Queue of cached images which needs their imports checked. */
[52431]312static PVERIFIERCACHEIMPORT volatile g_pVerifierCacheTodoImports = NULL;
[52947]313
314/** The windows path to dir \\SystemRoot\\System32 directory (technically
[58132]315 * this whatever \\KnownDlls\\KnownDllPath points to). */
[52947]316SUPSYSROOTDIRBUF g_System32WinPath;
[51770]317/** @ */
318
[52953]319/** Positive if the DLL notification callback has been registered, counts
320 * registration attempts as negative. */
321static int g_cDllNotificationRegistered = 0;
322/** The registration cookie of the DLL notification callback. */
323static PVOID g_pvDllNotificationCookie = NULL;
[52947]324
[51770]325/** Static error info structure used during init. */
326static RTERRINFOSTATIC g_ErrInfoStatic;
327
[52403]328/** In the assembly file. */
329extern "C" uint8_t g_abSupHardReadWriteExecPage[PAGE_SIZE];
[51770]330
[52523]331/** Whether we've patched our own LdrInitializeThunk or not. We do this to
332 * disable thread creation. */
333static bool g_fSupInitThunkSelfPatched;
334/** The backup of our own LdrInitializeThunk code, for enabling and disabling
335 * thread creation in this process. */
336static uint8_t g_abLdrInitThunkSelfBackup[16];
[52403]337
[52739]338/** Mask of adversaries that we've detected (SUPHARDNT_ADVERSARY_XXX). */
339static uint32_t g_fSupAdversaries = 0;
340/** @name SUPHARDNT_ADVERSARY_XXX - Adversaries
341 * @{ */
342/** Symantec endpoint protection or similar including SysPlant.sys. */
343#define SUPHARDNT_ADVERSARY_SYMANTEC_SYSPLANT RT_BIT_32(0)
[52741]344/** Symantec Norton 360. */
345#define SUPHARDNT_ADVERSARY_SYMANTEC_N360 RT_BIT_32(1)
[52739]346/** Avast! */
[52741]347#define SUPHARDNT_ADVERSARY_AVAST RT_BIT_32(2)
348/** TrendMicro OfficeScan and probably others. */
349#define SUPHARDNT_ADVERSARY_TRENDMICRO RT_BIT_32(3)
[53017]350/** TrendMicro potentially buggy sakfile.sys. */
351#define SUPHARDNT_ADVERSARY_TRENDMICRO_SAKFILE RT_BIT_32(4)
[52741]352/** McAfee. */
[53017]353#define SUPHARDNT_ADVERSARY_MCAFEE RT_BIT_32(5)
[52906]354/** Kaspersky or OEMs of it. */
[53017]355#define SUPHARDNT_ADVERSARY_KASPERSKY RT_BIT_32(6)
[52741]356/** Malwarebytes Anti-Malware (MBAM). */
[53017]357#define SUPHARDNT_ADVERSARY_MBAM RT_BIT_32(7)
[52741]358/** AVG Internet Security. */
[53017]359#define SUPHARDNT_ADVERSARY_AVG RT_BIT_32(8)
[52741]360/** Panda Security. */
[53017]361#define SUPHARDNT_ADVERSARY_PANDA RT_BIT_32(9)
[52741]362/** Microsoft Security Essentials. */
[53017]363#define SUPHARDNT_ADVERSARY_MSE RT_BIT_32(10)
[52795]364/** Comodo. */
[53017]365#define SUPHARDNT_ADVERSARY_COMODO RT_BIT_32(11)
[52906]366/** Check Point's Zone Alarm (may include Kaspersky). */
[53017]367#define SUPHARDNT_ADVERSARY_ZONE_ALARM RT_BIT_32(12)
[66484]368/** Digital guardian, old problematic version. */
369#define SUPHARDNT_ADVERSARY_DIGITAL_GUARDIAN_OLD RT_BIT_32(13)
370/** Digital guardian, new version. */
371#define SUPHARDNT_ADVERSARY_DIGITAL_GUARDIAN_NEW RT_BIT_32(14)
[60733]372/** Cylance protect or something (from googling, no available sample copy). */
[66484]373#define SUPHARDNT_ADVERSARY_CYLANCE RT_BIT_32(15)
[60733]374/** BeyondTrust / PowerBroker / something (googling, no available sample copy). */
[66484]375#define SUPHARDNT_ADVERSARY_BEYONDTRUST RT_BIT_32(16)
[60767]376/** Avecto / Defendpoint / Privilege Guard (details from support guy, hoping to get sample copy). */
[66484]377#define SUPHARDNT_ADVERSARY_AVECTO RT_BIT_32(17)
[52739]378/** Unknown adversary detected while waiting on child. */
379#define SUPHARDNT_ADVERSARY_UNKNOWN RT_BIT_32(31)
380/** @} */
[52523]381
[52739]382
[57358]383/*********************************************************************************************************************************
384* Internal Functions *
385*********************************************************************************************************************************/
[53220]386static NTSTATUS supR3HardenedScreenImage(HANDLE hFile, bool fImage, bool fIgnoreArch, PULONG pfAccess, PULONG pfProtect,
[52528]387 bool *pfCallRealApi, const char *pszCaller, bool fAvoidWinVerifyTrust,
[53045]388 bool *pfQuiet);
[52967]389static void supR3HardenedWinRegisterDllNotificationCallback(void);
390static void supR3HardenedWinReInstallHooks(bool fFirst);
391DECLASM(void) supR3HardenedEarlyProcessInitThunk(void);
[52403]392
[52953]393
[62677]394#if 0 /* unused */
[51770]395
396/**
397 * Simple wide char search routine.
398 *
399 * @returns Pointer to the first location of @a wcNeedle in @a pwszHaystack.
400 * NULL if not found.
401 * @param pwszHaystack Pointer to the string that should be searched.
402 * @param wcNeedle The character to search for.
403 */
404static PRTUTF16 suplibHardenedWStrChr(PCRTUTF16 pwszHaystack, RTUTF16 wcNeedle)
405{
406 for (;;)
407 {
408 RTUTF16 wcCur = *pwszHaystack;
409 if (wcCur == wcNeedle)
410 return (PRTUTF16)pwszHaystack;
411 if (wcCur == '\0')
412 return NULL;
413 pwszHaystack++;
414 }
415}
416
417
418/**
419 * Simple wide char string length routine.
420 *
421 * @returns The number of characters in the given string. (Excludes the
422 * terminator.)
423 * @param pwsz The string.
424 */
425static size_t suplibHardenedWStrLen(PCRTUTF16 pwsz)
426{
427 PCRTUTF16 pwszCur = pwsz;
428 while (*pwszCur != '\0')
429 pwszCur++;
430 return pwszCur - pwsz;
431}
432
[62677]433#endif /* unused */
[51770]434
[62677]435
[51770]436/**
[52949]437 * Our version of GetTickCount.
438 * @returns Millisecond timestamp.
439 */
440static uint64_t supR3HardenedWinGetMilliTS(void)
441{
442 PKUSER_SHARED_DATA pUserSharedData = (PKUSER_SHARED_DATA)(uintptr_t)0x7ffe0000;
443
444 /* use interrupt time */
445 LARGE_INTEGER Time;
446 do
447 {
448 Time.HighPart = pUserSharedData->InterruptTime.High1Time;
449 Time.LowPart = pUserSharedData->InterruptTime.LowPart;
450 } while (pUserSharedData->InterruptTime.High2Time != Time.HighPart);
451
452 return (uint64_t)Time.QuadPart / 10000;
453}
454
455
456
457/**
[51770]458 * Wrapper around LoadLibraryEx that deals with the UTF-8 to UTF-16 conversion
459 * and supplies the right flags.
460 *
461 * @returns Module handle on success, NULL on failure.
462 * @param pszName The full path to the DLL.
463 * @param fSystem32Only Whether to only look for imports in the system32
464 * directory. If set to false, the application
465 * directory is also searched.
[56746]466 * @param fMainFlags The main flags (giving the location), if the DLL
467 * being loaded is loaded from the app bin
468 * directory and import other DLLs from there. Pass
469 * 0 (= SUPSECMAIN_FLAGS_LOC_APP_BIN) if not
470 * applicable. Ignored if @a fSystem32Only is set.
471 *
472 * This is only needed to load VBoxRT.dll when
473 * executing a testcase from the testcase/ subdir.
[51770]474 */
[56746]475DECLHIDDEN(void *) supR3HardenedWinLoadLibrary(const char *pszName, bool fSystem32Only, uint32_t fMainFlags)
[51770]476{
477 WCHAR wszPath[RTPATH_MAX];
478 PRTUTF16 pwszPath = wszPath;
479 int rc = RTStrToUtf16Ex(pszName, RTSTR_MAX, &pwszPath, RT_ELEMENTS(wszPath), NULL);
480 if (RT_SUCCESS(rc))
481 {
482 while (*pwszPath)
483 {
484 if (*pwszPath == '/')
485 *pwszPath = '\\';
486 pwszPath++;
487 }
488
489 DWORD fFlags = 0;
490 if (g_uNtVerCombined >= SUP_MAKE_NT_VER_SIMPLE(6, 0))
491 {
[56733]492 fFlags |= LOAD_LIBRARY_SEARCH_SYSTEM32;
493 if (!fSystem32Only)
494 {
495 fFlags |= LOAD_LIBRARY_SEARCH_APPLICATION_DIR;
496 if (g_fSupLibHardenedDllSearchUserDirs)
497 fFlags |= LOAD_LIBRARY_SEARCH_USER_DIRS;
[56746]498 if ((fMainFlags & SUPSECMAIN_FLAGS_LOC_MASK) != SUPSECMAIN_FLAGS_LOC_APP_BIN)
499 fFlags |= LOAD_LIBRARY_SEARCH_DLL_LOAD_DIR;
[56733]500 }
[51770]501 }
502
503 void *pvRet = (void *)LoadLibraryExW(wszPath, NULL /*hFile*/, fFlags);
504
505 /* Vista, W7, W2K8R might not work without KB2533623, so retry with no flags. */
506 if ( !pvRet
507 && fFlags
508 && g_uNtVerCombined < SUP_MAKE_NT_VER_SIMPLE(6, 2)
[52940]509 && RtlGetLastWin32Error() == ERROR_INVALID_PARAMETER)
[51770]510 pvRet = (void *)LoadLibraryExW(wszPath, NULL /*hFile*/, 0);
511
512 return pvRet;
513 }
514 supR3HardenedFatal("RTStrToUtf16Ex failed on '%s': %Rrc", pszName, rc);
[62677]515 /* not reached */
[51770]516}
517
518
519/**
520 * Gets the internal index number of the file.
521 *
522 * @returns True if we got an index number, false if not.
523 * @param hFile The file in question.
524 * @param pIndexNumber where to return the index number.
525 */
526static bool supR3HardenedWinVerifyCacheGetIndexNumber(HANDLE hFile, PLARGE_INTEGER pIndexNumber)
527{
528 IO_STATUS_BLOCK Ios = RTNT_IO_STATUS_BLOCK_INITIALIZER;
529 NTSTATUS rcNt = NtQueryInformationFile(hFile, &Ios, pIndexNumber, sizeof(*pIndexNumber), FileInternalInformation);
530 if (NT_SUCCESS(rcNt))
531 rcNt = Ios.Status;
532#ifdef DEBUG_bird
533 if (!NT_SUCCESS(rcNt))
534 __debugbreak();
535#endif
536 return NT_SUCCESS(rcNt) && pIndexNumber->QuadPart != 0;
537}
538
539
540/**
[52403]541 * Calculates the hash value for the given UTF-16 path string.
[51770]542 *
543 * @returns Hash value.
544 * @param pUniStr String to hash.
545 */
546static uint32_t supR3HardenedWinVerifyCacheHashPath(PCUNICODE_STRING pUniStr)
547{
548 uint32_t uHash = 0;
549 unsigned cwcLeft = pUniStr->Length / sizeof(WCHAR);
550 PRTUTF16 pwc = pUniStr->Buffer;
551
552 while (cwcLeft-- > 0)
553 {
554 RTUTF16 wc = *pwc++;
[52403]555 if (wc < 0x80)
556 wc = wc != '/' ? RT_C_TO_LOWER(wc) : '\\';
[51770]557 uHash = wc + (uHash << 6) + (uHash << 16) - uHash;
558 }
559 return uHash;
560}
561
562
563/**
[52403]564 * Calculates the hash value for a directory + filename combo as if they were
565 * one single string.
566 *
567 * @returns Hash value.
568 * @param pawcDir The directory name.
569 * @param cwcDir The length of the directory name. RTSTR_MAX if
570 * not available.
571 * @param pszName The import name (UTF-8).
572 */
573static uint32_t supR3HardenedWinVerifyCacheHashDirAndFile(PCRTUTF16 pawcDir, uint32_t cwcDir, const char *pszName)
574{
575 uint32_t uHash = 0;
576 while (cwcDir-- > 0)
577 {
578 RTUTF16 wc = *pawcDir++;
579 if (wc < 0x80)
580 wc = wc != '/' ? RT_C_TO_LOWER(wc) : '\\';
581 uHash = wc + (uHash << 6) + (uHash << 16) - uHash;
582 }
583
584 unsigned char ch = '\\';
585 uHash = ch + (uHash << 6) + (uHash << 16) - uHash;
586
587 while ((ch = *pszName++) != '\0')
588 {
589 ch = RT_C_TO_LOWER(ch);
590 uHash = ch + (uHash << 6) + (uHash << 16) - uHash;
591 }
592
593 return uHash;
594}
595
596
597/**
598 * Verify string cache compare function.
599 *
600 * @returns true if the strings match, false if not.
601 * @param pawcLeft The left hand string.
602 * @param pawcRight The right hand string.
603 * @param cwcToCompare The number of chars to compare.
604 */
605static bool supR3HardenedWinVerifyCacheIsMatch(PCRTUTF16 pawcLeft, PCRTUTF16 pawcRight, uint32_t cwcToCompare)
606{
607 /* Try a quick memory compare first. */
608 if (memcmp(pawcLeft, pawcRight, cwcToCompare * sizeof(RTUTF16)) == 0)
609 return true;
610
611 /* Slow char by char compare. */
612 while (cwcToCompare-- > 0)
613 {
614 RTUTF16 wcLeft = *pawcLeft++;
615 RTUTF16 wcRight = *pawcRight++;
616 if (wcLeft != wcRight)
617 {
[52834]618 wcLeft = wcLeft != '/' ? RT_C_TO_LOWER(wcLeft) : '\\';
619 wcRight = wcRight != '/' ? RT_C_TO_LOWER(wcRight) : '\\';
[52403]620 if (wcLeft != wcRight)
621 return false;
622 }
623 }
624
625 return true;
626}
627
628
629
630/**
[51770]631 * Inserts the given verifier result into the cache.
632 *
633 * @param pUniStr The full path of the image.
634 * @param hFile The file handle - must either be entered into
635 * the cache or closed.
636 * @param rc The verifier result.
[52403]637 * @param fWinVerifyTrust Whether verified by WinVerifyTrust or not.
[52431]638 * @param fFlags The image verification flags.
[51770]639 */
[52431]640static void supR3HardenedWinVerifyCacheInsert(PCUNICODE_STRING pUniStr, HANDLE hFile, int rc,
641 bool fWinVerifyTrust, uint32_t fFlags)
[51770]642{
643 /*
[52403]644 * Allocate and initalize a new entry.
[51770]645 */
[52940]646 PVERIFIERCACHEENTRY pEntry = (PVERIFIERCACHEENTRY)RTMemAllocZ(sizeof(VERIFIERCACHEENTRY) + pUniStr->Length);
[52403]647 if (pEntry)
[51770]648 {
[52403]649 pEntry->pNext = NULL;
650 pEntry->pNextTodoWvt = NULL;
651 pEntry->hFile = hFile;
[52528]652 pEntry->uHash = supR3HardenedWinVerifyCacheHashPath(pUniStr);
[52403]653 pEntry->rc = rc;
[52431]654 pEntry->fFlags = fFlags;
[53045]655 pEntry->cHits = 0;
[52403]656 pEntry->fWinVerifyTrust = fWinVerifyTrust;
657 pEntry->cbPath = pUniStr->Length;
658 memcpy(pEntry->wszPath, pUniStr->Buffer, pUniStr->Length);
659 pEntry->wszPath[pUniStr->Length / sizeof(WCHAR)] = '\0';
660 pEntry->fIndexNumberValid = supR3HardenedWinVerifyCacheGetIndexNumber(hFile, &pEntry->IndexNumber);
661
[51770]662 /*
[52403]663 * Try insert it, careful with concurrent code as well as potential duplicates.
[51770]664 */
[52403]665 uint32_t iHashTab = pEntry->uHash % RT_ELEMENTS(g_apVerifierCache);
666 VERIFIERCACHEENTRY * volatile *ppEntry = &g_apVerifierCache[iHashTab];
667 for (;;)
[51770]668 {
[52403]669 if (ASMAtomicCmpXchgPtr(ppEntry, pEntry, NULL))
[51770]670 {
[52403]671 if (!fWinVerifyTrust)
672 do
673 pEntry->pNextTodoWvt = g_pVerifierCacheTodoWvt;
674 while (!ASMAtomicCmpXchgPtr(&g_pVerifierCacheTodoWvt, pEntry, pEntry->pNextTodoWvt));
[52375]675
[52403]676 SUP_DPRINTF(("supR3HardenedWinVerifyCacheInsert: %ls\n", pUniStr->Buffer));
677 return;
[51770]678 }
679
[52403]680 PVERIFIERCACHEENTRY pOther = *ppEntry;
681 if (!pOther)
682 continue;
683 if ( pOther->uHash == pEntry->uHash
684 && pOther->cbPath == pEntry->cbPath
[52535]685 && supR3HardenedWinVerifyCacheIsMatch(pOther->wszPath, pEntry->wszPath, pEntry->cbPath / sizeof(RTUTF16)))
[52403]686 break;
687 ppEntry = &pOther->pNext;
688 }
689
[52431]690 /* Duplicate entry (may happen due to races). */
[52940]691 RTMemFree(pEntry);
[51770]692 }
693 NtClose(hFile);
694}
695
696
697/**
698 * Looks up an entry in the verifier hash table.
699 *
700 * @return Pointer to the entry on if found, NULL if not.
701 * @param pUniStr The full path of the image.
702 * @param hFile The file handle.
703 */
704static PVERIFIERCACHEENTRY supR3HardenedWinVerifyCacheLookup(PCUNICODE_STRING pUniStr, HANDLE hFile)
705{
706 PRTUTF16 const pwszPath = pUniStr->Buffer;
707 uint16_t const cbPath = pUniStr->Length;
708 uint32_t uHash = supR3HardenedWinVerifyCacheHashPath(pUniStr);
709 uint32_t iHashTab = uHash % RT_ELEMENTS(g_apVerifierCache);
710 PVERIFIERCACHEENTRY pCur = g_apVerifierCache[iHashTab];
711 while (pCur)
712 {
713 if ( pCur->uHash == uHash
714 && pCur->cbPath == cbPath
[52403]715 && supR3HardenedWinVerifyCacheIsMatch(pCur->wszPath, pwszPath, cbPath / sizeof(RTUTF16)))
[51770]716 {
717
718 if (!pCur->fIndexNumberValid)
719 return pCur;
720 LARGE_INTEGER IndexNumber;
721 bool fIndexNumberValid = supR3HardenedWinVerifyCacheGetIndexNumber(hFile, &IndexNumber);
722 if ( fIndexNumberValid
723 && IndexNumber.QuadPart == pCur->IndexNumber.QuadPart)
724 return pCur;
725#ifdef DEBUG_bird
726 __debugbreak();
727#endif
728 }
729 pCur = pCur->pNext;
730 }
731 return NULL;
732}
733
734
735/**
[52403]736 * Looks up an import DLL in the verifier hash table.
737 *
738 * @return Pointer to the entry on if found, NULL if not.
739 * @param pawcDir The directory name.
740 * @param cwcDir The length of the directory name.
741 * @param pszName The import name (UTF-8).
742 */
743static PVERIFIERCACHEENTRY supR3HardenedWinVerifyCacheLookupImport(PCRTUTF16 pawcDir, uint32_t cwcDir, const char *pszName)
744{
745 uint32_t uHash = supR3HardenedWinVerifyCacheHashDirAndFile(pawcDir, cwcDir, pszName);
746 uint32_t iHashTab = uHash % RT_ELEMENTS(g_apVerifierCache);
747 uint32_t const cbPath = (uint32_t)((cwcDir + 1 + strlen(pszName)) * sizeof(RTUTF16));
748 PVERIFIERCACHEENTRY pCur = g_apVerifierCache[iHashTab];
749 while (pCur)
750 {
751 if ( pCur->uHash == uHash
752 && pCur->cbPath == cbPath)
753 {
754 if (supR3HardenedWinVerifyCacheIsMatch(pCur->wszPath, pawcDir, cwcDir))
755 {
756 if (pCur->wszPath[cwcDir] == '\\' || pCur->wszPath[cwcDir] == '/')
757 {
758 if (RTUtf16ICmpAscii(&pCur->wszPath[cwcDir + 1], pszName))
759 {
760 return pCur;
761 }
762 }
763 }
764 }
765
766 pCur = pCur->pNext;
767 }
768 return NULL;
769}
770
771
772/**
773 * Schedules the import DLLs for verification and entry into the cache.
774 *
775 * @param hLdrMod The loader module which imports should be
776 * scheduled for verification.
777 * @param pwszName The full NT path of the module.
778 */
779DECLHIDDEN(void) supR3HardenedWinVerifyCacheScheduleImports(RTLDRMOD hLdrMod, PCRTUTF16 pwszName)
780{
781 /*
782 * Any imports?
783 */
784 uint32_t cImports;
785 int rc = RTLdrQueryPropEx(hLdrMod, RTLDRPROP_IMPORT_COUNT, NULL /*pvBits*/, &cImports, sizeof(cImports), NULL);
786 if (RT_SUCCESS(rc))
787 {
788 if (cImports)
789 {
790 /*
791 * Figure out the DLL directory from pwszName.
792 */
793 PCRTUTF16 pawcDir = pwszName;
794 uint32_t cwcDir = 0;
795 uint32_t i = 0;
796 RTUTF16 wc;
797 while ((wc = pawcDir[i++]) != '\0')
798 if ((wc == '\\' || wc == '/' || wc == ':') && cwcDir + 2 != i)
799 cwcDir = i - 1;
800 if ( g_System32NtPath.UniStr.Length / sizeof(WCHAR) == cwcDir
801 && supR3HardenedWinVerifyCacheIsMatch(pawcDir, g_System32NtPath.UniStr.Buffer, cwcDir))
802 pawcDir = NULL;
803
804 /*
805 * Enumerate the imports.
806 */
807 for (i = 0; i < cImports; i++)
808 {
809 union
810 {
811 char szName[256];
812 uint32_t iImport;
813 } uBuf;
814 uBuf.iImport = i;
815 rc = RTLdrQueryPropEx(hLdrMod, RTLDRPROP_IMPORT_MODULE, NULL /*pvBits*/, &uBuf, sizeof(uBuf), NULL);
816 if (RT_SUCCESS(rc))
817 {
818 /*
819 * Skip kernel32, ntdll and API set stuff.
820 */
821 RTStrToLower(uBuf.szName);
822 if ( RTStrCmp(uBuf.szName, "kernel32.dll") == 0
823 || RTStrCmp(uBuf.szName, "kernelbase.dll") == 0
824 || RTStrCmp(uBuf.szName, "ntdll.dll") == 0
[57161]825 || RTStrNCmp(uBuf.szName, RT_STR_TUPLE("api-ms-win-")) == 0
826 || RTStrNCmp(uBuf.szName, RT_STR_TUPLE("ext-ms-win-")) == 0
827 )
[52403]828 {
829 continue;
830 }
831
832 /*
833 * Skip to the next one if it's already in the cache.
834 */
835 if (supR3HardenedWinVerifyCacheLookupImport(g_System32NtPath.UniStr.Buffer,
836 g_System32NtPath.UniStr.Length / sizeof(WCHAR),
837 uBuf.szName) != NULL)
838 {
839 SUP_DPRINTF(("supR3HardenedWinVerifyCacheScheduleImports: '%s' cached for system32\n", uBuf.szName));
840 continue;
841 }
[56733]842 if (supR3HardenedWinVerifyCacheLookupImport(g_SupLibHardenedAppBinNtPath.UniStr.Buffer,
843 g_SupLibHardenedAppBinNtPath.UniStr.Length / sizeof(CHAR),
[52403]844 uBuf.szName) != NULL)
845 {
846 SUP_DPRINTF(("supR3HardenedWinVerifyCacheScheduleImports: '%s' cached for appdir\n", uBuf.szName));
847 continue;
848 }
849 if (pawcDir && supR3HardenedWinVerifyCacheLookupImport(pawcDir, cwcDir, uBuf.szName) != NULL)
850 {
851 SUP_DPRINTF(("supR3HardenedWinVerifyCacheScheduleImports: '%s' cached for dll dir\n", uBuf.szName));
852 continue;
853 }
854
855 /* We could skip already scheduled modules, but that'll require serialization and extra work... */
856
857 /*
858 * Add it to the todo list.
859 */
860 SUP_DPRINTF(("supR3HardenedWinVerifyCacheScheduleImports: Import todo: #%u '%s'.\n", i, uBuf.szName));
861 uint32_t cbName = (uint32_t)strlen(uBuf.szName) + 1;
862 uint32_t cbNameAligned = RT_ALIGN_32(cbName, sizeof(RTUTF16));
863 uint32_t cbNeeded = RT_OFFSETOF(VERIFIERCACHEIMPORT, szName[cbNameAligned])
864 + (pawcDir ? (cwcDir + 1) * sizeof(RTUTF16) : 0);
[52940]865 PVERIFIERCACHEIMPORT pImport = (PVERIFIERCACHEIMPORT)RTMemAllocZ(cbNeeded);
[52403]866 if (pImport)
867 {
868 /* Init it. */
869 memcpy(pImport->szName, uBuf.szName, cbName);
870 if (!pawcDir)
871 {
872 pImport->cwcAltSearchDir = 0;
873 pImport->pwszAltSearchDir = NULL;
874 }
875 else
876 {
877 pImport->cwcAltSearchDir = cwcDir;
878 pImport->pwszAltSearchDir = (PRTUTF16)&pImport->szName[cbNameAligned];
879 memcpy(pImport->pwszAltSearchDir, pawcDir, cwcDir * sizeof(RTUTF16));
880 pImport->pwszAltSearchDir[cwcDir] = '\0';
881 }
882
883 /* Insert it. */
884 do
885 pImport->pNext = g_pVerifierCacheTodoImports;
886 while (!ASMAtomicCmpXchgPtr(&g_pVerifierCacheTodoImports, pImport, pImport->pNext));
887 }
888 }
889 else
890 SUP_DPRINTF(("RTLDRPROP_IMPORT_MODULE failed with rc=%Rrc i=%#x on '%ls'\n", rc, i, pwszName));
891 }
892 }
893 else
894 SUP_DPRINTF(("'%ls' has no imports\n", pwszName));
895 }
896 else
897 SUP_DPRINTF(("RTLDRPROP_IMPORT_COUNT failed with rc=%Rrc on '%ls'\n", rc, pwszName));
898}
899
900
901/**
902 * Processes the list of import todos.
903 */
904static void supR3HardenedWinVerifyCacheProcessImportTodos(void)
905{
906 /*
907 * Work until we've got nothing more todo.
908 */
909 for (;;)
910 {
911 PVERIFIERCACHEIMPORT pTodo = ASMAtomicXchgPtrT(&g_pVerifierCacheTodoImports, NULL, PVERIFIERCACHEIMPORT);
912 if (!pTodo)
913 break;
914 do
915 {
916 PVERIFIERCACHEIMPORT pCur = pTodo;
917 pTodo = pTodo->pNext;
918
919 /*
920 * Not in the cached already?
921 */
922 if ( !supR3HardenedWinVerifyCacheLookupImport(g_System32NtPath.UniStr.Buffer,
923 g_System32NtPath.UniStr.Length / sizeof(WCHAR),
924 pCur->szName)
[56733]925 && !supR3HardenedWinVerifyCacheLookupImport(g_SupLibHardenedAppBinNtPath.UniStr.Buffer,
926 g_SupLibHardenedAppBinNtPath.UniStr.Length / sizeof(WCHAR),
[52403]927 pCur->szName)
928 && ( pCur->cwcAltSearchDir == 0
929 || !supR3HardenedWinVerifyCacheLookupImport(pCur->pwszAltSearchDir, pCur->cwcAltSearchDir, pCur->szName)) )
930 {
931 /*
932 * Try locate the imported DLL and open it.
933 */
934 SUP_DPRINTF(("supR3HardenedWinVerifyCacheProcessImportTodos: Processing '%s'...\n", pCur->szName));
935
936 NTSTATUS rcNt;
[52741]937 NTSTATUS rcNtRedir = 0x22222222;
[52403]938 HANDLE hFile = INVALID_HANDLE_VALUE;
939 RTUTF16 wszPath[260 + 260]; /* Assumes we've limited the import name length to 256. */
940 AssertCompile(sizeof(wszPath) > sizeof(g_System32NtPath));
[52627]941
942 /*
943 * Check for DLL isolation / redirection / mapping.
944 */
945 size_t cwcName = 260;
946 PRTUTF16 pwszName = &wszPath[0];
947 int rc = RTStrToUtf16Ex(pCur->szName, RTSTR_MAX, &pwszName, cwcName, &cwcName);
948 if (RT_SUCCESS(rc))
[52403]949 {
[52627]950 UNICODE_STRING UniStrName;
951 UniStrName.Buffer = wszPath;
952 UniStrName.Length = (USHORT)cwcName * sizeof(WCHAR);
953 UniStrName.MaximumLength = UniStrName.Length + sizeof(WCHAR);
[52403]954
[52627]955 UNICODE_STRING UniStrStatic;
956 UniStrStatic.Buffer = &wszPath[cwcName + 1];
957 UniStrStatic.Length = 0;
[52632]958 UniStrStatic.MaximumLength = (USHORT)(sizeof(wszPath) - cwcName * sizeof(WCHAR) - sizeof(WCHAR));
[52627]959
960 static UNICODE_STRING const s_DefaultSuffix = RTNT_CONSTANT_UNISTR(L".dll");
961 UNICODE_STRING UniStrDynamic = { 0, 0, NULL };
962 PUNICODE_STRING pUniStrResult = NULL;
963
[52741]964 rcNtRedir = RtlDosApplyFileIsolationRedirection_Ustr(1 /*fFlags*/,
965 &UniStrName,
966 (PUNICODE_STRING)&s_DefaultSuffix,
967 &UniStrStatic,
968 &UniStrDynamic,
969 &pUniStrResult,
970 NULL /*pNewFlags*/,
971 NULL /*pcbFilename*/,
972 NULL /*pcbNeeded*/);
973 if (NT_SUCCESS(rcNtRedir))
[52627]974 {
[52741]975 IO_STATUS_BLOCK Ios = RTNT_IO_STATUS_BLOCK_INITIALIZER;
[52627]976 OBJECT_ATTRIBUTES ObjAttr;
977 InitializeObjectAttributes(&ObjAttr, pUniStrResult,
978 OBJ_CASE_INSENSITIVE, NULL /*hRootDir*/, NULL /*pSecDesc*/);
979 rcNt = NtCreateFile(&hFile,
980 FILE_READ_DATA | READ_CONTROL | SYNCHRONIZE,
981 &ObjAttr,
982 &Ios,
983 NULL /* Allocation Size*/,
984 FILE_ATTRIBUTE_NORMAL,
985 FILE_SHARE_READ,
986 FILE_OPEN,
987 FILE_NON_DIRECTORY_FILE | FILE_SYNCHRONOUS_IO_NONALERT,
988 NULL /*EaBuffer*/,
989 0 /*EaLength*/);
990 if (NT_SUCCESS(rcNt))
991 rcNt = Ios.Status;
[52741]992 if (NT_SUCCESS(rcNt))
993 {
994 /* For accurate logging. */
995 size_t cwcCopy = RT_MIN(pUniStrResult->Length / sizeof(RTUTF16), RT_ELEMENTS(wszPath) - 1);
996 memcpy(wszPath, pUniStrResult->Buffer, cwcCopy * sizeof(RTUTF16));
997 wszPath[cwcCopy] = '\0';
998 }
999 else
[52627]1000 hFile = INVALID_HANDLE_VALUE;
1001 RtlFreeUnicodeString(&UniStrDynamic);
1002 }
[52403]1003 }
[52627]1004 else
1005 SUP_DPRINTF(("supR3HardenedWinVerifyCacheProcessImportTodos: RTStrToUtf16Ex #1 failed: %Rrc\n", rc));
[52403]1006
[52627]1007 /*
1008 * If not something that gets remapped, do the half normal searching we need.
1009 */
1010 if (hFile == INVALID_HANDLE_VALUE)
[52403]1011 {
[52627]1012 struct
[52403]1013 {
[52627]1014 PRTUTF16 pawcDir;
1015 uint32_t cwcDir;
1016 } Tmp, aDirs[] =
1017 {
1018 { g_System32NtPath.UniStr.Buffer, g_System32NtPath.UniStr.Length / sizeof(WCHAR) },
[56733]1019 { g_SupLibHardenedExeNtPath.UniStr.Buffer, g_SupLibHardenedAppBinNtPath.UniStr.Length / sizeof(WCHAR) },
[52627]1020 { pCur->pwszAltSearchDir, pCur->cwcAltSearchDir },
1021 };
1022
1023 /* Search System32 first, unless it's a 'V*' or 'm*' name, the latter for msvcrt. */
1024 if ( pCur->szName[0] == 'v'
1025 || pCur->szName[0] == 'V'
1026 || pCur->szName[0] == 'm'
1027 || pCur->szName[0] == 'M')
1028 {
1029 Tmp = aDirs[0];
1030 aDirs[0] = aDirs[1];
1031 aDirs[1] = Tmp;
1032 }
1033
1034 for (uint32_t i = 0; i < RT_ELEMENTS(aDirs); i++)
1035 {
1036 if (aDirs[i].pawcDir && aDirs[i].cwcDir && aDirs[i].cwcDir < RT_ELEMENTS(wszPath) / 3 * 2)
[52403]1037 {
[52627]1038 memcpy(wszPath, aDirs[i].pawcDir, aDirs[i].cwcDir * sizeof(RTUTF16));
1039 uint32_t cwc = aDirs[i].cwcDir;
1040 wszPath[cwc++] = '\\';
1041 cwcName = RT_ELEMENTS(wszPath) - cwc;
1042 pwszName = &wszPath[cwc];
1043 rc = RTStrToUtf16Ex(pCur->szName, RTSTR_MAX, &pwszName, cwcName, &cwcName);
1044 if (RT_SUCCESS(rc))
1045 {
1046 IO_STATUS_BLOCK Ios = RTNT_IO_STATUS_BLOCK_INITIALIZER;
1047 UNICODE_STRING NtName;
1048 NtName.Buffer = wszPath;
1049 NtName.Length = (USHORT)((cwc + cwcName) * sizeof(WCHAR));
1050 NtName.MaximumLength = NtName.Length + sizeof(WCHAR);
1051 OBJECT_ATTRIBUTES ObjAttr;
1052 InitializeObjectAttributes(&ObjAttr, &NtName, OBJ_CASE_INSENSITIVE, NULL /*hRootDir*/, NULL /*pSecDesc*/);
[52403]1053
[52627]1054 rcNt = NtCreateFile(&hFile,
1055 FILE_READ_DATA | READ_CONTROL | SYNCHRONIZE,
1056 &ObjAttr,
1057 &Ios,
1058 NULL /* Allocation Size*/,
1059 FILE_ATTRIBUTE_NORMAL,
1060 FILE_SHARE_READ,
1061 FILE_OPEN,
1062 FILE_NON_DIRECTORY_FILE | FILE_SYNCHRONOUS_IO_NONALERT,
1063 NULL /*EaBuffer*/,
1064 0 /*EaLength*/);
1065 if (NT_SUCCESS(rcNt))
1066 rcNt = Ios.Status;
1067 if (NT_SUCCESS(rcNt))
1068 break;
1069 hFile = INVALID_HANDLE_VALUE;
1070 }
1071 else
1072 SUP_DPRINTF(("supR3HardenedWinVerifyCacheProcessImportTodos: RTStrToUtf16Ex #2 failed: %Rrc\n", rc));
[52403]1073 }
1074 }
1075 }
1076
1077 /*
1078 * If we successfully opened it, verify it and cache the result.
1079 */
1080 if (hFile != INVALID_HANDLE_VALUE)
1081 {
[52741]1082 SUP_DPRINTF(("supR3HardenedWinVerifyCacheProcessImportTodos: '%s' -> '%ls' [rcNtRedir=%#x]\n",
1083 pCur->szName, wszPath, rcNtRedir));
[52403]1084
1085 ULONG fAccess = 0;
1086 ULONG fProtect = 0;
1087 bool fCallRealApi = false;
[53220]1088 rcNt = supR3HardenedScreenImage(hFile, true /*fImage*/, false /*fIgnoreArch*/, &fAccess, &fProtect,
1089 &fCallRealApi, "Imports", false /*fAvoidWinVerifyTrust*/, NULL /*pfQuiet*/);
[52403]1090 NtClose(hFile);
1091 }
1092 else
1093 SUP_DPRINTF(("supR3HardenedWinVerifyCacheProcessImportTodos: Failed to locate '%s'\n", pCur->szName));
1094 }
1095 else
1096 SUP_DPRINTF(("supR3HardenedWinVerifyCacheProcessImportTodos: '%s' is in the cache.\n", pCur->szName));
1097
[52940]1098 RTMemFree(pCur);
[52403]1099 } while (pTodo);
1100 }
1101}
1102
1103
1104/**
[52431]1105 * Processes the list of WinVerifyTrust todos.
1106 */
1107static void supR3HardenedWinVerifyCacheProcessWvtTodos(void)
1108{
1109 PVERIFIERCACHEENTRY pReschedule = NULL;
1110 PVERIFIERCACHEENTRY volatile *ppReschedLastNext = NULL;
1111
1112 /*
1113 * Work until we've got nothing more todo.
1114 */
1115 for (;;)
1116 {
1117 if (!supHardenedWinIsWinVerifyTrustCallable())
1118 break;
1119 PVERIFIERCACHEENTRY pTodo = ASMAtomicXchgPtrT(&g_pVerifierCacheTodoWvt, NULL, PVERIFIERCACHEENTRY);
1120 if (!pTodo)
1121 break;
1122 do
1123 {
1124 PVERIFIERCACHEENTRY pCur = pTodo;
1125 pTodo = pTodo->pNextTodoWvt;
1126 pCur->pNextTodoWvt = NULL;
1127
1128 if ( !pCur->fWinVerifyTrust
1129 && RT_SUCCESS(pCur->rc))
1130 {
1131 bool fWinVerifyTrust = false;
1132 int rc = supHardenedWinVerifyImageTrust(pCur->hFile, pCur->wszPath, pCur->fFlags, pCur->rc,
1133 &fWinVerifyTrust, NULL /* pErrInfo*/);
1134 if (RT_FAILURE(rc) || fWinVerifyTrust)
1135 {
1136 SUP_DPRINTF(("supR3HardenedWinVerifyCacheProcessWvtTodos: %d (was %d) fWinVerifyTrust=%d for '%ls'\n",
1137 rc, pCur->rc, fWinVerifyTrust, pCur->wszPath));
1138 pCur->fWinVerifyTrust = true;
1139 pCur->rc = rc;
1140 }
1141 else
1142 {
1143 /* Retry it at a later time. */
1144 SUP_DPRINTF(("supR3HardenedWinVerifyCacheProcessWvtTodos: %d (was %d) fWinVerifyTrust=%d for '%ls' [rescheduled]\n",
1145 rc, pCur->rc, fWinVerifyTrust, pCur->wszPath));
1146 if (!pReschedule)
1147 ppReschedLastNext = &pCur->pNextTodoWvt;
1148 pCur->pNextTodoWvt = pReschedule;
1149 }
1150 }
1151 /* else: already processed. */
1152 } while (pTodo);
1153 }
1154
1155 /*
1156 * Anything to reschedule.
1157 */
1158 if (pReschedule)
1159 {
1160 do
1161 *ppReschedLastNext = g_pVerifierCacheTodoWvt;
1162 while (!ASMAtomicCmpXchgPtr(&g_pVerifierCacheTodoWvt, pReschedule, *ppReschedLastNext));
1163 }
1164}
1165
1166
1167/**
[58730]1168 * Translates VBox status code (from supHardenedWinVerifyImageTrust) to an NT
1169 * status.
1170 *
1171 * @returns NT status.
1172 * @param rc VBox status code.
1173 */
1174static NTSTATUS supR3HardenedScreenImageCalcStatus(int rc)
1175{
1176 /* This seems to be what LdrLoadDll returns when loading a 32-bit DLL into
1177 a 64-bit process. At least here on windows 10 (2015-11-xx).
1178
1179 NtCreateSection probably returns something different, possibly a warning,
1180 we currently don't distinguish between the too, so we stick with the
1181 LdrLoadDll one as it's definitely an error.*/
1182 if (rc == VERR_LDR_ARCH_MISMATCH)
1183 return STATUS_INVALID_IMAGE_FORMAT;
1184
1185 return STATUS_TRUST_FAILURE;
1186}
1187
1188
1189/**
[53220]1190 * Screens an image file or file mapped with execute access.
1191 *
1192 * @returns NT status code.
1193 * @param hFile The file handle.
1194 * @param fImage Set if image file mapping being made
1195 * (NtCreateSection thing).
1196 * @param fIgnoreArch Using the DONT_RESOLVE_DLL_REFERENCES flag,
1197 * which also implies that DLL init / term code
1198 * isn't called, so the architecture should be
1199 * ignored.
1200 * @param pfAccess Pointer to the NtCreateSection access flags,
1201 * so we can modify them if necessary.
1202 * @param pfProtect Pointer to the NtCreateSection protection
1203 * flags, so we can modify them if necessary.
1204 * @param pfCallRealApi Whether it's ok to go on to the real API.
1205 * @param pszCaller Who is calling (for debugging / logging).
1206 * @param fAvoidWinVerifyTrust Whether we should avoid WinVerifyTrust.
1207 * @param pfQuiet Where to return whether to be quiet about
1208 * this image in the log (i.e. we've seen it
1209 * lots of times already). Optional.
1210 */
1211static NTSTATUS supR3HardenedScreenImage(HANDLE hFile, bool fImage, bool fIgnoreArch, PULONG pfAccess, PULONG pfProtect,
[53045]1212 bool *pfCallRealApi, const char *pszCaller, bool fAvoidWinVerifyTrust, bool *pfQuiet)
[51770]1213{
[52375]1214 *pfCallRealApi = false;
[53045]1215 if (pfQuiet)
1216 *pfQuiet = false;
[52375]1217
1218 /*
1219 * Query the name of the file, making sure to zero terminator the
1220 * string. (2nd half of buffer is used for error info, see below.)
1221 */
1222 union
[51770]1223 {
[52375]1224 UNICODE_STRING UniStr;
1225 uint8_t abBuffer[sizeof(UNICODE_STRING) + 2048 * sizeof(WCHAR)];
1226 } uBuf;
1227 RT_ZERO(uBuf);
1228 ULONG cbNameBuf;
1229 NTSTATUS rcNt = NtQueryObject(hFile, ObjectNameInformation, &uBuf, sizeof(uBuf) - sizeof(WCHAR) - 128, &cbNameBuf);
1230 if (!NT_SUCCESS(rcNt))
1231 {
[52403]1232 supR3HardenedError(VINF_SUCCESS, false,
1233 "supR3HardenedScreenImage/%s: NtQueryObject -> %#x (fImage=%d fProtect=%#x fAccess=%#x)\n",
1234 pszCaller, fImage, *pfProtect, *pfAccess);
[52375]1235 return rcNt;
1236 }
1237
[60480]1238 if (!RTNtPathFindPossible8dot3Name(uBuf.UniStr.Buffer))
1239 cbNameBuf += sizeof(WCHAR);
1240 else
[52375]1241 {
1242 uBuf.UniStr.MaximumLength = sizeof(uBuf) - 128;
[60480]1243 RTNtPathExpand8dot3Path(&uBuf.UniStr, true /*fPathOnly*/);
1244 cbNameBuf = (uintptr_t)uBuf.UniStr.Buffer + uBuf.UniStr.Length + sizeof(WCHAR) - (uintptr_t)&uBuf.abBuffer[0];
[52375]1245 }
1246
1247 /*
1248 * Check the cache.
1249 */
1250 PVERIFIERCACHEENTRY pCacheHit = supR3HardenedWinVerifyCacheLookup(&uBuf.UniStr, hFile);
[52431]1251 if (pCacheHit)
[52375]1252 {
[53045]1253 /* Do hit accounting and figure whether we need to be quiet or not. */
1254 uint32_t cHits = ASMAtomicIncU32(&pCacheHit->cHits);
1255 bool const fQuiet = cHits >= 8 && !RT_IS_POWER_OF_TWO(cHits);
1256 if (pfQuiet)
1257 *pfQuiet = fQuiet;
1258
[52431]1259 /* If we haven't done the WinVerifyTrust thing, do it if we can. */
1260 if ( !pCacheHit->fWinVerifyTrust
1261 && RT_SUCCESS(pCacheHit->rc)
1262 && supHardenedWinIsWinVerifyTrustCallable() )
1263 {
1264 if (!fAvoidWinVerifyTrust)
1265 {
1266 SUP_DPRINTF(("supR3HardenedScreenImage/%s: cache hit (%Rrc) on %ls [redoing WinVerifyTrust]\n",
1267 pszCaller, pCacheHit->rc, pCacheHit->wszPath));
1268
1269 bool fWinVerifyTrust = false;
1270 int rc = supHardenedWinVerifyImageTrust(pCacheHit->hFile, pCacheHit->wszPath, pCacheHit->fFlags, pCacheHit->rc,
1271 &fWinVerifyTrust, NULL /* pErrInfo*/);
1272 if (RT_FAILURE(rc) || fWinVerifyTrust)
1273 {
1274 SUP_DPRINTF(("supR3HardenedScreenImage/%s: %d (was %d) fWinVerifyTrust=%d for '%ls'\n",
1275 pszCaller, rc, pCacheHit->rc, fWinVerifyTrust, pCacheHit->wszPath));
1276 pCacheHit->fWinVerifyTrust = true;
1277 pCacheHit->rc = rc;
1278 }
1279 else
1280 SUP_DPRINTF(("supR3HardenedScreenImage/%s: WinVerifyTrust not available, rescheduling %ls\n",
1281 pszCaller, pCacheHit->wszPath));
1282 }
1283 else
1284 SUP_DPRINTF(("supR3HardenedScreenImage/%s: cache hit (%Rrc) on %ls [avoiding WinVerifyTrust]\n",
1285 pszCaller, pCacheHit->rc, pCacheHit->wszPath));
1286 }
[53045]1287 else if (!fQuiet || !pCacheHit->fWinVerifyTrust)
[52431]1288 SUP_DPRINTF(("supR3HardenedScreenImage/%s: cache hit (%Rrc) on %ls%s\n",
1289 pszCaller, pCacheHit->rc, pCacheHit->wszPath, pCacheHit->fWinVerifyTrust ? "" : " [lacks WinVerifyTrust]"));
1290
1291 /* Return the cached value. */
[52375]1292 if (RT_SUCCESS(pCacheHit->rc))
[51770]1293 {
[52375]1294 *pfCallRealApi = true;
1295 return STATUS_SUCCESS;
1296 }
[52528]1297
[53045]1298 if (!fQuiet)
[52528]1299 supR3HardenedError(VINF_SUCCESS, false,
[53045]1300 "supR3HardenedScreenImage/%s: cached rc=%Rrc fImage=%d fProtect=%#x fAccess=%#x cHits=%u %ls\n",
1301 pszCaller, pCacheHit->rc, fImage, *pfProtect, *pfAccess, cHits, uBuf.UniStr.Buffer);
[58730]1302 return supR3HardenedScreenImageCalcStatus(pCacheHit->rc);
[52375]1303 }
1304
1305 /*
1306 * On XP the loader might hand us handles with just FILE_EXECUTE and
1307 * SYNCHRONIZE, the means reading will fail later on. Also, we need
1308 * READ_CONTROL access to check the file ownership later on, and non
1309 * of the OS versions seems be giving us that. So, in effect we
1310 * more or less always reopen the file here.
1311 */
1312 HANDLE hMyFile = NULL;
1313 rcNt = NtDuplicateObject(NtCurrentProcess(), hFile, NtCurrentProcess(),
1314 &hMyFile,
1315 FILE_READ_DATA | READ_CONTROL | SYNCHRONIZE,
1316 0 /* Handle attributes*/, 0 /* Options */);
1317 if (!NT_SUCCESS(rcNt))
1318 {
1319 if (rcNt == STATUS_ACCESS_DENIED)
1320 {
1321 IO_STATUS_BLOCK Ios = RTNT_IO_STATUS_BLOCK_INITIALIZER;
1322 OBJECT_ATTRIBUTES ObjAttr;
1323 InitializeObjectAttributes(&ObjAttr, &uBuf.UniStr, OBJ_CASE_INSENSITIVE, NULL /*hRootDir*/, NULL /*pSecDesc*/);
1324
1325 rcNt = NtCreateFile(&hMyFile,
1326 FILE_READ_DATA | READ_CONTROL | SYNCHRONIZE,
1327 &ObjAttr,
1328 &Ios,
1329 NULL /* Allocation Size*/,
1330 FILE_ATTRIBUTE_NORMAL,
1331 FILE_SHARE_READ,
1332 FILE_OPEN,
1333 FILE_NON_DIRECTORY_FILE | FILE_SYNCHRONOUS_IO_NONALERT,
1334 NULL /*EaBuffer*/,
1335 0 /*EaLength*/);
1336 if (NT_SUCCESS(rcNt))
1337 rcNt = Ios.Status;
[51770]1338 if (!NT_SUCCESS(rcNt))
1339 {
1340 supR3HardenedError(VINF_SUCCESS, false,
[52403]1341 "supR3HardenedScreenImage/%s: Failed to duplicate and open the file: rcNt=%#x hFile=%p %ls\n",
1342 pszCaller, rcNt, hFile, uBuf.UniStr.Buffer);
[51770]1343 return rcNt;
1344 }
1345
[52375]1346 /* Check that we've got the same file. */
1347 LARGE_INTEGER idMyFile, idInFile;
1348 bool fMyValid = supR3HardenedWinVerifyCacheGetIndexNumber(hMyFile, &idMyFile);
1349 bool fInValid = supR3HardenedWinVerifyCacheGetIndexNumber(hFile, &idInFile);
1350 if ( fMyValid
1351 && ( fMyValid != fInValid
1352 || idMyFile.QuadPart != idInFile.QuadPart))
[52039]1353 {
[51770]1354 supR3HardenedError(VINF_SUCCESS, false,
[52403]1355 "supR3HardenedScreenImage/%s: Re-opened has different ID that input: %#llx vx %#llx (%ls)\n",
1356 pszCaller, rcNt, idMyFile.QuadPart, idInFile.QuadPart, uBuf.UniStr.Buffer);
[52375]1357 NtClose(hMyFile);
[51770]1358 return STATUS_TRUST_FAILURE;
1359 }
[52375]1360 }
1361 else
1362 {
[52403]1363 SUP_DPRINTF(("supR3HardenedScreenImage/%s: NtDuplicateObject -> %#x\n", pszCaller, rcNt));
[51770]1364#ifdef DEBUG
1365
[52403]1366 supR3HardenedError(VINF_SUCCESS, false,
1367 "supR3HardenedScreenImage/%s: NtDuplicateObject(,%#x,) failed: %#x\n", pszCaller, hFile, rcNt);
[51770]1368#endif
[52375]1369 hMyFile = hFile;
1370 }
1371 }
[51770]1372
[52375]1373 /*
1374 * Special Kludge for Windows XP and W2K3 and their stupid attempts
1375 * at mapping a hidden XML file called c:\Windows\WindowsShell.Manifest
1376 * with executable access. The image bit isn't set, fortunately.
1377 */
1378 if ( !fImage
1379 && uBuf.UniStr.Length > g_System32NtPath.UniStr.Length - sizeof(L"System32") + sizeof(WCHAR)
1380 && memcmp(uBuf.UniStr.Buffer, g_System32NtPath.UniStr.Buffer,
1381 g_System32NtPath.UniStr.Length - sizeof(L"System32") + sizeof(WCHAR)) == 0)
1382 {
1383 PRTUTF16 pwszName = &uBuf.UniStr.Buffer[(g_System32NtPath.UniStr.Length - sizeof(L"System32") + sizeof(WCHAR)) / sizeof(WCHAR)];
1384 if (RTUtf16ICmpAscii(pwszName, "WindowsShell.Manifest") == 0)
1385 {
[51770]1386 /*
[52375]1387 * Drop all executable access to the mapping and let it continue.
[51770]1388 */
[52403]1389 SUP_DPRINTF(("supR3HardenedScreenImage/%s: Applying the drop-exec-kludge for '%ls'\n", pszCaller, uBuf.UniStr.Buffer));
[52375]1390 if (*pfAccess & SECTION_MAP_EXECUTE)
1391 *pfAccess = (*pfAccess & ~SECTION_MAP_EXECUTE) | SECTION_MAP_READ;
1392 if (*pfProtect & PAGE_EXECUTE)
1393 *pfProtect = (*pfProtect & ~PAGE_EXECUTE) | PAGE_READONLY;
1394 *pfProtect = (*pfProtect & ~UINT32_C(0xf0)) | ((*pfProtect & UINT32_C(0xe0)) >> 4);
1395 if (hMyFile != hFile)
1396 NtClose(hMyFile);
1397 *pfCallRealApi = true;
1398 return STATUS_SUCCESS;
1399 }
1400 }
[51770]1401
[52404]1402#ifndef VBOX_PERMIT_EVEN_MORE
[52375]1403 /*
1404 * Check the path. We don't allow DLLs to be loaded from just anywhere:
1405 * 1. System32 - normal code or cat signing, owner TrustedInstaller.
1406 * 2. WinSxS - normal code or cat signing, owner TrustedInstaller.
1407 * 3. VirtualBox - kernel code signing and integrity checks.
1408 * 4. AppPatchDir - normal code or cat signing, owner TrustedInstaller.
1409 * 5. Program Files - normal code or cat signing, owner TrustedInstaller.
1410 * 6. Common Files - normal code or cat signing, owner TrustedInstaller.
1411 * 7. x86 variations of 4 & 5 - ditto.
1412 */
1413 uint32_t fFlags = 0;
1414 if (supHardViUniStrPathStartsWithUniStr(&uBuf.UniStr, &g_System32NtPath.UniStr, true /*fCheckSlash*/))
1415 fFlags |= SUPHNTVI_F_ALLOW_CAT_FILE_VERIFICATION | SUPHNTVI_F_TRUSTED_INSTALLER_OWNER;
1416 else if (supHardViUniStrPathStartsWithUniStr(&uBuf.UniStr, &g_WinSxSNtPath.UniStr, true /*fCheckSlash*/))
1417 fFlags |= SUPHNTVI_F_ALLOW_CAT_FILE_VERIFICATION | SUPHNTVI_F_TRUSTED_INSTALLER_OWNER;
[56733]1418 else if (supHardViUniStrPathStartsWithUniStr(&uBuf.UniStr, &g_SupLibHardenedAppBinNtPath.UniStr, true /*fCheckSlash*/))
[52375]1419 fFlags |= SUPHNTVI_F_REQUIRE_KERNEL_CODE_SIGNING | SUPHNTVI_F_REQUIRE_SIGNATURE_ENFORCEMENT;
[52404]1420# ifdef VBOX_PERMIT_MORE
[52375]1421 else if (supHardViIsAppPatchDir(uBuf.UniStr.Buffer, uBuf.UniStr.Length / sizeof(WCHAR)))
1422 fFlags |= SUPHNTVI_F_ALLOW_CAT_FILE_VERIFICATION | SUPHNTVI_F_TRUSTED_INSTALLER_OWNER;
1423 else if (supHardViUniStrPathStartsWithUniStr(&uBuf.UniStr, &g_ProgramFilesNtPath.UniStr, true /*fCheckSlash*/))
1424 fFlags |= SUPHNTVI_F_ALLOW_CAT_FILE_VERIFICATION | SUPHNTVI_F_TRUSTED_INSTALLER_OWNER;
1425 else if (supHardViUniStrPathStartsWithUniStr(&uBuf.UniStr, &g_CommonFilesNtPath.UniStr, true /*fCheckSlash*/))
1426 fFlags |= SUPHNTVI_F_ALLOW_CAT_FILE_VERIFICATION | SUPHNTVI_F_TRUSTED_INSTALLER_OWNER;
[52404]1427# ifdef RT_ARCH_AMD64
[52375]1428 else if (supHardViUniStrPathStartsWithUniStr(&uBuf.UniStr, &g_ProgramFilesX86NtPath.UniStr, true /*fCheckSlash*/))
1429 fFlags |= SUPHNTVI_F_ALLOW_CAT_FILE_VERIFICATION | SUPHNTVI_F_TRUSTED_INSTALLER_OWNER;
1430 else if (supHardViUniStrPathStartsWithUniStr(&uBuf.UniStr, &g_CommonFilesX86NtPath.UniStr, true /*fCheckSlash*/))
1431 fFlags |= SUPHNTVI_F_ALLOW_CAT_FILE_VERIFICATION | SUPHNTVI_F_TRUSTED_INSTALLER_OWNER;
[52404]1432# endif
[52365]1433# endif
[52404]1434# ifdef VBOX_PERMIT_VISUAL_STUDIO_PROFILING
[52375]1435 /* Hack to allow profiling our code with Visual Studio. */
1436 else if ( uBuf.UniStr.Length > sizeof(L"\\SamplingRuntime.dll")
1437 && memcmp(uBuf.UniStr.Buffer + (uBuf.UniStr.Length - sizeof(L"\\SamplingRuntime.dll") + sizeof(WCHAR)) / sizeof(WCHAR),
1438 L"\\SamplingRuntime.dll", sizeof(L"\\SamplingRuntime.dll") - sizeof(WCHAR)) == 0 )
1439 {
1440 if (hMyFile != hFile)
1441 NtClose(hMyFile);
1442 *pfCallRealApi = true;
1443 return STATUS_SUCCESS;
1444 }
[52404]1445# endif
[52375]1446 else
1447 {
1448 supR3HardenedError(VINF_SUCCESS, false,
[52484]1449 "supR3HardenedScreenImage/%s: Not a trusted location: '%ls' (fImage=%d fProtect=%#x fAccess=%#x)\n",
[52403]1450 pszCaller, uBuf.UniStr.Buffer, fImage, *pfAccess, *pfProtect);
[52375]1451 if (hMyFile != hFile)
1452 NtClose(hMyFile);
1453 return STATUS_TRUST_FAILURE;
1454 }
[51770]1455
[52404]1456#else /* VBOX_PERMIT_EVEN_MORE */
[52375]1457 /*
[52404]1458 * Require trusted installer + some kind of signature on everything, except
1459 * for the VBox bits where we require kernel code signing and special
1460 * integrity checks.
1461 */
1462 uint32_t fFlags = 0;
[56733]1463 if (supHardViUniStrPathStartsWithUniStr(&uBuf.UniStr, &g_SupLibHardenedAppBinNtPath.UniStr, true /*fCheckSlash*/))
[52404]1464 fFlags |= SUPHNTVI_F_REQUIRE_KERNEL_CODE_SIGNING | SUPHNTVI_F_REQUIRE_SIGNATURE_ENFORCEMENT;
1465 else
1466 fFlags |= SUPHNTVI_F_ALLOW_CAT_FILE_VERIFICATION | SUPHNTVI_F_TRUSTED_INSTALLER_OWNER;
1467#endif /* VBOX_PERMIT_EVEN_MORE */
1468
1469 /*
[52375]1470 * Do the verification. For better error message we borrow what's
1471 * left of the path buffer for an RTERRINFO buffer.
1472 */
[53220]1473 if (fIgnoreArch)
1474 fFlags |= SUPHNTVI_F_IGNORE_ARCHITECTURE;
[52375]1475 RTERRINFO ErrInfo;
1476 RTErrInfoInit(&ErrInfo, (char *)&uBuf.abBuffer[cbNameBuf], sizeof(uBuf) - cbNameBuf);
[51770]1477
[52406]1478 int rc;
[52403]1479 bool fWinVerifyTrust = false;
[52634]1480 rc = supHardenedWinVerifyImageByHandle(hMyFile, uBuf.UniStr.Buffer, fFlags, fAvoidWinVerifyTrust, &fWinVerifyTrust, &ErrInfo);
[52375]1481 if (RT_FAILURE(rc))
1482 {
1483 supR3HardenedError(VINF_SUCCESS, false,
[52403]1484 "supR3HardenedScreenImage/%s: rc=%Rrc fImage=%d fProtect=%#x fAccess=%#x %ls: %s\n",
1485 pszCaller, rc, fImage, *pfAccess, *pfProtect, uBuf.UniStr.Buffer, ErrInfo.pszMsg);
[52375]1486 if (hMyFile != hFile)
[52679]1487 supR3HardenedWinVerifyCacheInsert(&uBuf.UniStr, hMyFile, rc, fWinVerifyTrust, fFlags);
[58730]1488 return supR3HardenedScreenImageCalcStatus(rc);
[52375]1489 }
1490
[52403]1491 /*
[52431]1492 * Insert into the cache.
[52403]1493 */
[52431]1494 if (hMyFile != hFile)
1495 supR3HardenedWinVerifyCacheInsert(&uBuf.UniStr, hMyFile, rc, fWinVerifyTrust, fFlags);
[52403]1496
[52375]1497 *pfCallRealApi = true;
1498 return STATUS_SUCCESS;
1499}
1500
1501
1502/**
1503 * Preloads a file into the verify cache if possible.
1504 *
1505 * This is used to avoid known cyclic LoadLibrary issues with WinVerifyTrust.
1506 *
1507 * @param pwszName The name of the DLL to verify.
1508 */
1509DECLHIDDEN(void) supR3HardenedWinVerifyCachePreload(PCRTUTF16 pwszName)
1510{
1511 HANDLE hFile = RTNT_INVALID_HANDLE_VALUE;
1512 IO_STATUS_BLOCK Ios = RTNT_IO_STATUS_BLOCK_INITIALIZER;
1513
1514 UNICODE_STRING UniStr;
1515 UniStr.Buffer = (PWCHAR)pwszName;
1516 UniStr.Length = (USHORT)(RTUtf16Len(pwszName) * sizeof(WCHAR));
1517 UniStr.MaximumLength = UniStr.Length + sizeof(WCHAR);
1518
1519 OBJECT_ATTRIBUTES ObjAttr;
1520 InitializeObjectAttributes(&ObjAttr, &UniStr, OBJ_CASE_INSENSITIVE, NULL /*hRootDir*/, NULL /*pSecDesc*/);
1521
1522 NTSTATUS rcNt = NtCreateFile(&hFile,
1523 FILE_READ_DATA | READ_CONTROL | SYNCHRONIZE,
1524 &ObjAttr,
1525 &Ios,
1526 NULL /* Allocation Size*/,
1527 FILE_ATTRIBUTE_NORMAL,
1528 FILE_SHARE_READ,
1529 FILE_OPEN,
1530 FILE_NON_DIRECTORY_FILE | FILE_SYNCHRONOUS_IO_NONALERT,
1531 NULL /*EaBuffer*/,
1532 0 /*EaLength*/);
1533 if (NT_SUCCESS(rcNt))
1534 rcNt = Ios.Status;
1535 if (!NT_SUCCESS(rcNt))
1536 {
[52403]1537 SUP_DPRINTF(("supR3HardenedWinVerifyCachePreload: Error %#x opening '%ls'.\n", rcNt, pwszName));
[52375]1538 return;
1539 }
1540
1541 ULONG fAccess = 0;
1542 ULONG fProtect = 0;
1543 bool fCallRealApi;
1544 //SUP_DPRINTF(("supR3HardenedWinVerifyCachePreload: scanning %ls\n", pwszName));
[53220]1545 supR3HardenedScreenImage(hFile, false, false /*fIgnoreArch*/, &fAccess, &fProtect, &fCallRealApi, "preload",
1546 false /*fAvoidWinVerifyTrust*/, NULL /*pfQuiet*/);
[52375]1547 //SUP_DPRINTF(("supR3HardenedWinVerifyCachePreload: done %ls\n", pwszName));
1548
1549 NtClose(hFile);
1550}
1551
1552
1553
1554/**
1555 * Hook that monitors NtCreateSection calls.
1556 *
1557 * @returns NT status code.
1558 * @param phSection Where to return the section handle.
1559 * @param fAccess The desired access.
1560 * @param pObjAttribs The object attributes (optional).
1561 * @param pcbSection The section size (optional).
1562 * @param fProtect The max section protection.
1563 * @param fAttribs The section attributes.
1564 * @param hFile The file to create a section from (optional).
1565 */
1566static NTSTATUS NTAPI
1567supR3HardenedMonitor_NtCreateSection(PHANDLE phSection, ACCESS_MASK fAccess, POBJECT_ATTRIBUTES pObjAttribs,
1568 PLARGE_INTEGER pcbSection, ULONG fProtect, ULONG fAttribs, HANDLE hFile)
1569{
[67977]1570 bool fNeedUncChecking = false;
[52375]1571 if ( hFile != NULL
1572 && hFile != INVALID_HANDLE_VALUE)
1573 {
1574 bool const fImage = RT_BOOL(fAttribs & (SEC_IMAGE | SEC_PROTECTED_IMAGE));
1575 bool const fExecMap = RT_BOOL(fAccess & SECTION_MAP_EXECUTE);
1576 bool const fExecProt = RT_BOOL(fProtect & (PAGE_EXECUTE | PAGE_EXECUTE_READ | PAGE_EXECUTE_WRITECOPY
1577 | PAGE_EXECUTE_READWRITE));
1578 if (fImage || fExecMap || fExecProt)
1579 {
[67977]1580 fNeedUncChecking = true;
[52940]1581 DWORD dwSavedLastError = RtlGetLastWin32Error();
[52403]1582
[52375]1583 bool fCallRealApi;
1584 //SUP_DPRINTF(("supR3HardenedMonitor_NtCreateSection: 1\n"));
[53220]1585 NTSTATUS rcNt = supR3HardenedScreenImage(hFile, fImage, true /*fIgnoreArch*/, &fAccess, &fProtect, &fCallRealApi,
[53045]1586 "NtCreateSection", true /*fAvoidWinVerifyTrust*/, NULL /*pfQuiet*/);
[52375]1587 //SUP_DPRINTF(("supR3HardenedMonitor_NtCreateSection: 2 rcNt=%#x fCallRealApi=%#x\n", rcNt, fCallRealApi));
[52403]1588
[52940]1589 RtlRestoreLastWin32Error(dwSavedLastError);
[52403]1590
[52375]1591 if (!NT_SUCCESS(rcNt))
1592 return rcNt;
1593 Assert(fCallRealApi);
1594 if (!fCallRealApi)
[51770]1595 return STATUS_TRUST_FAILURE;
[52403]1596
[51770]1597 }
1598 }
1599
1600 /*
1601 * Call checked out OK, call the original.
1602 */
[67977]1603 NTSTATUS rcNtReal = g_pfnNtCreateSectionReal(phSection, fAccess, pObjAttribs, pcbSection, fProtect, fAttribs, hFile);
1604
1605 /*
1606 * Check that the image that got mapped bear some resemblance to the one that was
1607 * requested. Apparently there are ways to trick the NT cache manager to map a
1608 * file different from hFile into memory using local UNC accesses.
1609 */
1610 if ( NT_SUCCESS(rcNtReal)
1611 && fNeedUncChecking)
1612 {
1613 DWORD dwSavedLastError = RtlGetLastWin32Error();
1614
1615 bool fOkay = false;
1616
1617 /* To get the name of the file backing the section, we unfortunately have to map it. */
1618 SIZE_T cbView = 0;
1619 PVOID pvTmpMap = NULL;
1620 NTSTATUS rcNt = NtMapViewOfSection(*phSection, NtCurrentProcess(), &pvTmpMap, 0, 0, NULL /*poffSection*/, &cbView,
1621 ViewUnmap, MEM_TOP_DOWN, PAGE_EXECUTE);
1622 if (NT_SUCCESS(rcNt))
1623 {
1624 /* Query the name. */
1625 union
1626 {
1627 UNICODE_STRING UniStr;
1628 RTUTF16 awcBuf[512];
1629 } uBuf;
1630 RT_ZERO(uBuf);
1631 SIZE_T cbActual = 0;
1632 NTSTATUS rcNtQuery = NtQueryVirtualMemory(NtCurrentProcess(), pvTmpMap, MemorySectionName,
1633 &uBuf, sizeof(uBuf) - sizeof(RTUTF16), &cbActual);
1634
1635 /* Unmap the view. */
1636 rcNt = NtUnmapViewOfSection(NtCurrentProcess(), pvTmpMap);
1637 if (!NT_SUCCESS(rcNt))
1638 SUP_DPRINTF(("supR3HardenedMonitor_NtCreateSection: NtUnmapViewOfSection failed on %p (hSection=%p, hFile=%p) with %#x!\n",
1639 pvTmpMap, *phSection, hFile, rcNt));
1640
1641 /* Process the name query result. */
1642 if (NT_SUCCESS(rcNtQuery))
1643 {
1644 static UNICODE_STRING const s_UncPrefix = RTNT_CONSTANT_UNISTR(L"\\Device\\Mup");
1645 if (!supHardViUniStrPathStartsWithUniStr(&uBuf.UniStr, &s_UncPrefix, true /*fCheckSlash*/))
1646 fOkay = true;
1647 else
1648 supR3HardenedError(VINF_SUCCESS, false,
1649 "supR3HardenedMonitor_NtCreateSection: Image section with UNC path is not trusted: '%.*ls'\n",
1650 uBuf.UniStr.Length / sizeof(RTUTF16), uBuf.UniStr.Buffer);
1651 }
1652 else
1653 SUP_DPRINTF(("supR3HardenedMonitor_NtCreateSection: NtQueryVirtualMemory failed on %p (hFile=%p) with %#x -> STATUS_TRUST_FAILURE\n",
1654 *phSection, hFile, rcNt));
1655 }
1656 else
1657 SUP_DPRINTF(("supR3HardenedMonitor_NtCreateSection: NtMapViewOfSection failed on %p (hFile=%p) with %#x -> STATUS_TRUST_FAILURE\n",
1658 *phSection, hFile, rcNt));
1659 if (!fOkay)
1660 {
1661 NtClose(*phSection);
1662 *phSection = INVALID_HANDLE_VALUE;
1663 RtlRestoreLastWin32Error(dwSavedLastError);
1664 return STATUS_TRUST_FAILURE;
1665 }
1666
1667 RtlRestoreLastWin32Error(dwSavedLastError);
1668 }
1669 return rcNtReal;
[51770]1670}
1671
1672
[52403]1673/**
[67979]1674 * Checks if the given name is a valid ApiSet name.
1675 *
1676 * This is only called on likely looking names.
1677 *
1678 * @returns true if ApiSet name, false if not.
1679 * @param pName The name to check out.
1680 */
1681static bool supR3HardenedIsApiSetDll(PUNICODE_STRING pName)
1682{
1683 /*
1684 * API added in Windows 8, or so they say.
1685 */
1686 if (ApiSetQueryApiSetPresence != NULL)
1687 {
1688 BOOLEAN fPresent = FALSE;
1689 NTSTATUS rcNt = ApiSetQueryApiSetPresence(pName, &fPresent);
1690 SUP_DPRINTF(("supR3HardenedIsApiSetDll: ApiSetQueryApiSetPresence(%.*ls) -> %#x, fPresent=%d\n",
1691 pName->Length / sizeof(WCHAR), pName->Buffer, rcNt, fPresent));
1692 return fPresent != 0;
1693 }
1694
1695 /*
1696 * Fallback needed for Windows 7. Fortunately, there aren't too many fake DLLs here.
1697 */
[67981]1698 if ( g_uNtVerCombined >= SUP_NT_VER_W70
1699 && ( supHardViUtf16PathStartsWithEx(pName->Buffer, pName->Length / sizeof(WCHAR),
1700 L"api-ms-win-", 11, false /*fCheckSlash*/)
1701 || supHardViUtf16PathStartsWithEx(pName->Buffer, pName->Length / sizeof(WCHAR),
1702 L"ext-ms-win-", 11, false /*fCheckSlash*/) ))
[67979]1703 {
1704#define MY_ENTRY(a) { a, sizeof(a) - 1 }
1705 static const struct { const char *psz; size_t cch; } s_aKnownSets[] =
1706 {
1707 MY_ENTRY("api-ms-win-core-console-l1-1-0 "),
1708 MY_ENTRY("api-ms-win-core-datetime-l1-1-0"),
1709 MY_ENTRY("api-ms-win-core-debug-l1-1-0"),
1710 MY_ENTRY("api-ms-win-core-delayload-l1-1-0"),
1711 MY_ENTRY("api-ms-win-core-errorhandling-l1-1-0"),
1712 MY_ENTRY("api-ms-win-core-fibers-l1-1-0"),
1713 MY_ENTRY("api-ms-win-core-file-l1-1-0"),
1714 MY_ENTRY("api-ms-win-core-handle-l1-1-0"),
1715 MY_ENTRY("api-ms-win-core-heap-l1-1-0"),
1716 MY_ENTRY("api-ms-win-core-interlocked-l1-1-0"),
1717 MY_ENTRY("api-ms-win-core-io-l1-1-0"),
1718 MY_ENTRY("api-ms-win-core-libraryloader-l1-1-0"),
1719 MY_ENTRY("api-ms-win-core-localization-l1-1-0"),
1720 MY_ENTRY("api-ms-win-core-localregistry-l1-1-0"),
1721 MY_ENTRY("api-ms-win-core-memory-l1-1-0"),
1722 MY_ENTRY("api-ms-win-core-misc-l1-1-0"),
1723 MY_ENTRY("api-ms-win-core-namedpipe-l1-1-0"),
1724 MY_ENTRY("api-ms-win-core-processenvironment-l1-1-0"),
1725 MY_ENTRY("api-ms-win-core-processthreads-l1-1-0"),
1726 MY_ENTRY("api-ms-win-core-profile-l1-1-0"),
1727 MY_ENTRY("api-ms-win-core-rtlsupport-l1-1-0"),
1728 MY_ENTRY("api-ms-win-core-string-l1-1-0"),
1729 MY_ENTRY("api-ms-win-core-synch-l1-1-0"),
1730 MY_ENTRY("api-ms-win-core-sysinfo-l1-1-0"),
1731 MY_ENTRY("api-ms-win-core-threadpool-l1-1-0"),
1732 MY_ENTRY("api-ms-win-core-ums-l1-1-0"),
1733 MY_ENTRY("api-ms-win-core-util-l1-1-0"),
1734 MY_ENTRY("api-ms-win-core-xstate-l1-1-0"),
1735 MY_ENTRY("api-ms-win-security-base-l1-1-0"),
1736 MY_ENTRY("api-ms-win-security-lsalookup-l1-1-0"),
1737 MY_ENTRY("api-ms-win-security-sddl-l1-1-0"),
1738 MY_ENTRY("api-ms-win-service-core-l1-1-0"),
1739 MY_ENTRY("api-ms-win-service-management-l1-1-0"),
1740 MY_ENTRY("api-ms-win-service-management-l2-1-0"),
1741 MY_ENTRY("api-ms-win-service-winsvc-l1-1-0"),
1742 };
1743#undef MY_ENTRY
1744
1745 /* drop the dll suffix if present. */
1746 PCRTUTF16 pawcName = pName->Buffer;
1747 size_t cwcName = pName->Length / sizeof(WCHAR);
1748 if ( cwcName > 5
1749 && (pawcName[cwcName - 1] == 'l' || pawcName[cwcName - 1] == 'L')
1750 && (pawcName[cwcName - 2] == 'l' || pawcName[cwcName - 2] == 'L')
1751 && (pawcName[cwcName - 3] == 'd' || pawcName[cwcName - 3] == 'D')
1752 && pawcName[cwcName - 4] == '.')
1753 cwcName -= 4;
1754
1755 /* Search the table. */
1756 for (size_t i = 0; i < RT_ELEMENTS(s_aKnownSets); i++)
1757 if ( cwcName == s_aKnownSets[i].cch
1758 && RTUtf16NICmpAscii(pawcName, s_aKnownSets[i].psz, cwcName) == 0)
1759 {
1760 SUP_DPRINTF(("supR3HardenedIsApiSetDll: '%.*ls' -> true\n", pName->Length / sizeof(WCHAR)));
1761 return true;
1762 }
1763
1764 SUP_DPRINTF(("supR3HardenedIsApiSetDll: Warning! '%.*ls' looks like an API set, but it's not in the list!\n",
1765 pName->Length / sizeof(WCHAR), pName->Buffer));
1766 }
1767
1768 SUP_DPRINTF(("supR3HardenedIsApiSetDll: '%.*ls' -> false\n", pName->Length / sizeof(WCHAR)));
1769 return false;
1770}
1771
1772
1773/**
1774 * Checks whether the given unicode string contains a path separator and at
1775 * least one dash.
1776 *
1777 * This is used to check for likely ApiSet name. So far, all the pseudo DLL
1778 * names include multiple dashes, so we use that as a criteria for recognizing
1779 * them. By happy coincident, most regular DLLs doesn't include dashes.
1780 *
1781 * @returns true if it contains path separator, false if only a name.
1782 * @param pPath The path to check.
1783 */
1784static bool supR3HardenedHasDashButNoPath(PUNICODE_STRING pPath)
1785{
1786 size_t cDashes = 0;
1787 size_t cwcLeft = pPath->Length / sizeof(WCHAR);
1788 PCRTUTF16 pwc = pPath->Buffer;
1789 while (cwcLeft-- > 0)
1790 {
1791 RTUTF16 wc = *pwc++;
1792 switch (wc)
1793 {
1794 default:
1795 break;
1796
1797 case '-':
1798 cDashes++;
1799 break;
1800
1801 case '\\':
1802 case '/':
1803 case ':':
1804 return false;
1805 }
1806 }
1807 return cDashes > 0;
1808}
1809
1810
1811/**
[52704]1812 * Helper for supR3HardenedMonitor_LdrLoadDll.
1813 *
1814 * @returns NT status code.
1815 * @param pwszPath The path destination buffer.
1816 * @param cwcPath The size of the path buffer.
1817 * @param pUniStrResult The result string.
1818 * @param pOrgName The orignal name (for errors).
1819 * @param pcwc Where to return the actual length.
1820 */
1821static NTSTATUS supR3HardenedCopyRedirectionResult(WCHAR *pwszPath, size_t cwcPath, PUNICODE_STRING pUniStrResult,
1822 PUNICODE_STRING pOrgName, UINT *pcwc)
1823{
1824 UINT cwc;
1825 *pcwc = cwc = pUniStrResult->Length / sizeof(WCHAR);
1826 if (pUniStrResult->Buffer == pwszPath)
1827 pwszPath[cwc] = '\0';
1828 else
1829 {
1830 if (cwc > cwcPath - 1)
1831 {
1832 supR3HardenedError(VINF_SUCCESS, false,
1833 "supR3HardenedMonitor_LdrLoadDll: Name too long: %.*ls -> %.*ls (RtlDosApplyFileIoslationRedirection_Ustr)\n",
1834 pOrgName->Length / sizeof(WCHAR), pOrgName->Buffer,
1835 pUniStrResult->Length / sizeof(WCHAR), pUniStrResult->Buffer);
1836 return STATUS_NAME_TOO_LONG;
1837 }
1838 memcpy(&pwszPath[0], pUniStrResult->Buffer, pUniStrResult->Length);
1839 pwszPath[cwc] = '\0';
1840 }
1841 return STATUS_SUCCESS;
1842}
1843
1844
1845/**
[66525]1846 * Helper for supR3HardenedMonitor_LdrLoadDll that compares the name part of the
1847 * input path against a ASCII name string of a given length.
1848 *
1849 * @returns true if the name part matches
1850 * @param pPath The LdrLoadDll input path.
1851 * @param pszName The name to try match it with.
1852 * @param cchName The name length.
1853 */
1854static bool supR3HardenedIsFilenameMatchDll(PUNICODE_STRING pPath, const char *pszName, size_t cchName)
1855{
1856 if (pPath->Length < cchName * 2)
1857 return false;
1858 PCRTUTF16 pwszTmp = &pPath->Buffer[pPath->Length / sizeof(RTUTF16) - cchName];
1859 if ( pPath->Length != cchName
1860 && pwszTmp[-1] != '\\'
1861 && pwszTmp[-1] != '/')
1862 return false;
1863 return RTUtf16ICmpAscii(pwszTmp, pszName) == 0;
1864}
1865
1866
1867/**
[52403]1868 * Hooks that intercepts LdrLoadDll calls.
1869 *
1870 * Two purposes:
1871 * -# Enforce our own search path restrictions.
1872 * -# Prevalidate DLLs about to be loaded so we don't upset the loader data
1873 * by doing it from within the NtCreateSection hook (WinVerifyTrust
1874 * seems to be doing harm there on W7/32).
1875 *
1876 * @returns
1877 * @param pwszSearchPath The search path to use.
1878 * @param pfFlags Flags on input. DLL characteristics or something
1879 * on return?
1880 * @param pName The name of the module.
1881 * @param phMod Where the handle of the loaded DLL is to be
1882 * returned to the caller.
1883 */
1884static NTSTATUS NTAPI
1885supR3HardenedMonitor_LdrLoadDll(PWSTR pwszSearchPath, PULONG pfFlags, PUNICODE_STRING pName, PHANDLE phMod)
1886{
[53051]1887 DWORD dwSavedLastError = RtlGetLastWin32Error();
1888 PUNICODE_STRING const pOrgName = pName;
1889 NTSTATUS rcNt;
[52403]1890
1891 /*
[52953]1892 * Make sure the DLL notification callback is registered. If we could, we
1893 * would've done this during early process init, but due to lack of heap
1894 * and uninitialized loader lock, it's not possible that early on.
1895 *
1896 * The callback protects our NtDll hooks from getting unhooked by
1897 * "friendly" fire from the AV crowd.
1898 */
1899 supR3HardenedWinRegisterDllNotificationCallback();
1900
1901 /*
[52431]1902 * Process WinVerifyTrust todo before and after.
1903 */
1904 supR3HardenedWinVerifyCacheProcessWvtTodos();
1905
1906 /*
[52403]1907 * Reject things we don't want to deal with.
1908 */
1909 if (!pName || pName->Length == 0)
1910 {
1911 supR3HardenedError(VINF_SUCCESS, false, "supR3HardenedMonitor_LdrLoadDll: name is NULL or have a zero length.\n");
[52679]1912 SUP_DPRINTF(("supR3HardenedMonitor_LdrLoadDll: returns rcNt=%#x (pName=%p)\n", STATUS_INVALID_PARAMETER, pName));
[52940]1913 RtlRestoreLastWin32Error(dwSavedLastError);
[52403]1914 return STATUS_INVALID_PARAMETER;
1915 }
[67977]1916 PCWCHAR const pawcOrgName = pName->Buffer;
1917 uint32_t const cwcOrgName = pName->Length / sizeof(WCHAR);
1918
[52679]1919 /*SUP_DPRINTF(("supR3HardenedMonitor_LdrLoadDll: pName=%.*ls *pfFlags=%#x pwszSearchPath=%p:%ls\n",
[52627]1920 (unsigned)pName->Length / sizeof(WCHAR), pName->Buffer, pfFlags ? *pfFlags : UINT32_MAX, pwszSearchPath,
[52679]1921 !((uintptr_t)pwszSearchPath & 1) && (uintptr_t)pwszSearchPath >= 0x2000U ? pwszSearchPath : L"<flags>"));*/
[52403]1922
1923 /*
1924 * Reject long paths that's close to the 260 limit without looking.
1925 */
[67977]1926 if (cwcOrgName > 256)
[52403]1927 {
1928 supR3HardenedError(VINF_SUCCESS, false, "supR3HardenedMonitor_LdrLoadDll: too long name: %#x bytes\n", pName->Length);
[52484]1929 SUP_DPRINTF(("supR3HardenedMonitor_LdrLoadDll: returns rcNt=%#x\n", STATUS_NAME_TOO_LONG));
[52940]1930 RtlRestoreLastWin32Error(dwSavedLastError);
[52403]1931 return STATUS_NAME_TOO_LONG;
1932 }
1933
1934 /*
[67977]1935 * Reject all UNC-like paths as we cannot trust non-local files at all.
1936 * Note! We may have to relax this to deal with long path specifications and NT pass thrus.
1937 */
1938 if ( cwcOrgName >= 3
1939 && RTPATH_IS_SLASH(pawcOrgName[0])
1940 && RTPATH_IS_SLASH(pawcOrgName[1])
1941 && !RTPATH_IS_SLASH(pawcOrgName[2]))
1942 {
1943 supR3HardenedError(VINF_SUCCESS, false, "supR3HardenedMonitor_LdrLoadDll: rejecting UNC name '%.*ls'\n", cwcOrgName, pawcOrgName);
1944 SUP_DPRINTF(("supR3HardenedMonitor_LdrLoadDll: returns rcNt=%#x\n", STATUS_REDIRECTOR_NOT_STARTED));
1945 RtlRestoreLastWin32Error(dwSavedLastError);
1946 return STATUS_REDIRECTOR_NOT_STARTED;
1947 }
1948
1949 /*
[60936]1950 * Reject PGHook.dll as it creates a thread from its DllMain that breaks
1951 * our preconditions respawning the 2nd process, resulting in
1952 * VERR_SUP_VP_THREAD_NOT_ALONE. The DLL is being loaded by a user APC
1953 * scheduled during kernel32.dll load notification from a kernel driver,
1954 * so failing the load attempt should not upset anyone.
1955 */
1956 if (g_enmSupR3HardenedMainState == SUPR3HARDENEDMAINSTATE_WIN_EARLY_STUB_DEVICE_OPENED)
1957 {
1958 static const struct { const char *psz; size_t cch; } s_aUnwantedEarlyDlls[] =
1959 {
1960 { RT_STR_TUPLE("PGHook.dll") },
1961 };
1962 for (unsigned i = 0; i < RT_ELEMENTS(s_aUnwantedEarlyDlls); i++)
[66525]1963 if (supR3HardenedIsFilenameMatchDll(pName, s_aUnwantedEarlyDlls[i].psz, s_aUnwantedEarlyDlls[i].cch))
1964 {
1965 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",
1966 pName->Length / sizeof(RTUTF16), pName->Buffer));
1967 return STATUS_TOO_MANY_THREADS;
1968 }
[60936]1969 }
1970
1971 /*
[67968]1972 * Resolve the path, copying the result into wszPath
[52403]1973 */
[53051]1974 NTSTATUS rcNtResolve = STATUS_SUCCESS;
[52403]1975 bool fSkipValidation = false;
[53821]1976 bool fCheckIfLoaded = false;
[52403]1977 WCHAR wszPath[260];
[52704]1978 static UNICODE_STRING const s_DefaultSuffix = RTNT_CONSTANT_UNISTR(L".dll");
1979 UNICODE_STRING UniStrStatic = { 0, (USHORT)sizeof(wszPath) - sizeof(WCHAR), wszPath };
1980 UNICODE_STRING UniStrDynamic = { 0, 0, NULL };
1981 PUNICODE_STRING pUniStrResult = NULL;
[52403]1982 UNICODE_STRING ResolvedName;
[52704]1983
[67968]1984 /*
1985 * Process the name a little, checking if it needs a DLL suffix and is pathless.
1986 */
1987 uint32_t offLastSlash = UINT32_MAX;
1988 uint32_t offLastDot = UINT32_MAX;
[67977]1989 for (uint32_t i = 0; i < cwcOrgName; i++)
1990 switch (pawcOrgName[i])
[67968]1991 {
1992 case '\\':
1993 case '/':
1994 offLastSlash = i;
1995 offLastDot = UINT32_MAX;
1996 break;
1997 case '.':
1998 offLastDot = i;
1999 break;
2000 }
2001 bool const fNeedDllSuffix = offLastDot == UINT32_MAX;
[67977]2002 //bool const fTrailingDot = offLastDot == cwcOrgName - 1;
[67968]2003
2004 /*
2005 * Absolute path?
2006 */
[67977]2007 if ( ( cwcOrgName >= 4
2008 && RT_C_IS_ALPHA(pawcOrgName[0])
2009 && pawcOrgName[1] == ':'
2010 && RTPATH_IS_SLASH(pawcOrgName[2]) )
2011 || ( cwcOrgName >= 1
2012 && RTPATH_IS_SLASH(pawcOrgName[0]) )
[52403]2013 )
2014 {
[53051]2015 rcNtResolve = RtlDosApplyFileIsolationRedirection_Ustr(1 /*fFlags*/,
2016 pName,
2017 (PUNICODE_STRING)&s_DefaultSuffix,
2018 &UniStrStatic,
2019 &UniStrDynamic,
2020 &pUniStrResult,
2021 NULL /*pNewFlags*/,
2022 NULL /*pcbFilename*/,
2023 NULL /*pcbNeeded*/);
2024 if (NT_SUCCESS(rcNtResolve))
[52704]2025 {
2026 UINT cwc;
2027 rcNt = supR3HardenedCopyRedirectionResult(wszPath, RT_ELEMENTS(wszPath), pUniStrResult, pName, &cwc);
2028 RtlFreeUnicodeString(&UniStrDynamic);
2029 if (!NT_SUCCESS(rcNt))
2030 {
2031 SUP_DPRINTF(("supR3HardenedMonitor_LdrLoadDll: returns rcNt=%#x\n", rcNt));
[52940]2032 RtlRestoreLastWin32Error(dwSavedLastError);
[52704]2033 return rcNt;
2034 }
2035
2036 ResolvedName.Buffer = wszPath;
2037 ResolvedName.Length = (USHORT)(cwc * sizeof(WCHAR));
2038 ResolvedName.MaximumLength = ResolvedName.Length + sizeof(WCHAR);
2039
2040 SUP_DPRINTF(("supR3HardenedMonitor_LdrLoadDll: '%.*ls' -> '%.*ls' [redir]\n",
2041 (unsigned)pName->Length / sizeof(WCHAR), pName->Buffer,
2042 ResolvedName.Length / sizeof(WCHAR), ResolvedName.Buffer, rcNt));
2043 pName = &ResolvedName;
2044 }
2045 else
2046 {
[67968]2047 /* Copy the path. */
[67977]2048 memcpy(wszPath, pawcOrgName, cwcOrgName * sizeof(WCHAR));
2049 if (!fNeedDllSuffix)
2050 wszPath[cwcOrgName] = '\0';
2051 else
[67968]2052 {
[67977]2053 if (cwcOrgName + 4 >= RT_ELEMENTS(wszPath))
[67968]2054 {
2055 supR3HardenedError(VINF_SUCCESS, false,
[67977]2056 "supR3HardenedMonitor_LdrLoadDll: Name too long (abs): %.*ls\n", cwcOrgName, pawcOrgName);
[67968]2057 SUP_DPRINTF(("supR3HardenedMonitor_LdrLoadDll: returns rcNt=%#x\n", STATUS_NAME_TOO_LONG));
2058 RtlRestoreLastWin32Error(dwSavedLastError);
2059 return STATUS_NAME_TOO_LONG;
2060 }
[67977]2061 memcpy(&wszPath[cwcOrgName], L".dll", 5 * sizeof(WCHAR));
[67968]2062 }
[52704]2063 }
[52403]2064 }
2065 /*
[52627]2066 * Not an absolute path. Check if it's one of those special API set DLLs
2067 * or something we're known to use but should be taken from WinSxS.
[52403]2068 */
[67979]2069 else if ( supR3HardenedHasDashButNoPath(pName)
2070 && supR3HardenedIsApiSetDll(pName))
[52403]2071 {
2072 memcpy(wszPath, pName->Buffer, pName->Length);
2073 wszPath[pName->Length / sizeof(WCHAR)] = '\0';
2074 fSkipValidation = true;
2075 }
2076 /*
2077 * Not an absolute path or special API set. There are two alternatives
2078 * now, either there is no path at all or there is a relative path. We
2079 * will resolve it to an absolute path in either case, failing the call
[52627]2080 * if we can't.
[52403]2081 */
2082 else
2083 {
2084 /*
2085 * Reject relative paths for now as they might be breakout attempts.
2086 */
2087 if (offLastSlash != UINT32_MAX)
2088 {
2089 supR3HardenedError(VINF_SUCCESS, false,
2090 "supR3HardenedMonitor_LdrLoadDll: relative name not permitted: %.*ls\n",
[67977]2091 cwcOrgName, pawcOrgName);
[52484]2092 SUP_DPRINTF(("supR3HardenedMonitor_LdrLoadDll: returns rcNt=%#x\n", STATUS_OBJECT_NAME_INVALID));
[52940]2093 RtlRestoreLastWin32Error(dwSavedLastError);
[52403]2094 return STATUS_OBJECT_NAME_INVALID;
2095 }
2096
2097 /*
[52627]2098 * Perform dll redirection to WinSxS such. We using an undocumented
2099 * API here, which as always is a bit risky... ASSUMES that the API
2100 * returns a full DOS path.
[52403]2101 */
[52704]2102 UINT cwc;
[53051]2103 rcNtResolve = RtlDosApplyFileIsolationRedirection_Ustr(1 /*fFlags*/,
2104 pName,
2105 (PUNICODE_STRING)&s_DefaultSuffix,
2106 &UniStrStatic,
2107 &UniStrDynamic,
2108 &pUniStrResult,
2109 NULL /*pNewFlags*/,
2110 NULL /*pcbFilename*/,
2111 NULL /*pcbNeeded*/);
2112 if (NT_SUCCESS(rcNtResolve))
[52403]2113 {
[52704]2114 rcNt = supR3HardenedCopyRedirectionResult(wszPath, RT_ELEMENTS(wszPath), pUniStrResult, pName, &cwc);
2115 RtlFreeUnicodeString(&UniStrDynamic);
2116 if (!NT_SUCCESS(rcNt))
[52627]2117 {
[52704]2118 SUP_DPRINTF(("supR3HardenedMonitor_LdrLoadDll: returns rcNt=%#x\n", rcNt));
[52940]2119 RtlRestoreLastWin32Error(dwSavedLastError);
[52704]2120 return rcNt;
[52627]2121 }
[52403]2122 }
2123 else
2124 {
[52627]2125 /*
2126 * Search for the DLL. Only System32 is allowed as the target of
2127 * a search on the API level, all VBox calls will have full paths.
[53821]2128 * If the DLL is not in System32, we will resort to check if it's
2129 * refering to an already loaded DLL (fCheckIfLoaded).
[52627]2130 */
[52947]2131 AssertCompile(sizeof(g_System32WinPath.awcBuffer) <= sizeof(wszPath));
2132 cwc = g_System32WinPath.UniStr.Length / sizeof(RTUTF16); Assert(cwc > 2);
[67977]2133 if (cwc + 1 + cwcOrgName + fNeedDllSuffix * 4 >= RT_ELEMENTS(wszPath))
[52627]2134 {
2135 supR3HardenedError(VINF_SUCCESS, false,
[67977]2136 "supR3HardenedMonitor_LdrLoadDll: Name too long (system32): %.*ls\n", cwcOrgName, pawcOrgName);
[52627]2137 SUP_DPRINTF(("supR3HardenedMonitor_LdrLoadDll: returns rcNt=%#x\n", STATUS_NAME_TOO_LONG));
[52940]2138 RtlRestoreLastWin32Error(dwSavedLastError);
[52627]2139 return STATUS_NAME_TOO_LONG;
2140 }
[52947]2141 memcpy(wszPath, g_System32WinPath.UniStr.Buffer, cwc * sizeof(RTUTF16));
[52627]2142 wszPath[cwc++] = '\\';
[67977]2143 memcpy(&wszPath[cwc], pawcOrgName, cwcOrgName * sizeof(WCHAR));
2144 cwc += cwcOrgName;
[52627]2145 if (!fNeedDllSuffix)
2146 wszPath[cwc] = '\0';
2147 else
2148 {
2149 memcpy(&wszPath[cwc], L".dll", 5 * sizeof(WCHAR));
2150 cwc += 4;
2151 }
[53821]2152 fCheckIfLoaded = true;
[52403]2153 }
2154
2155 ResolvedName.Buffer = wszPath;
2156 ResolvedName.Length = (USHORT)(cwc * sizeof(WCHAR));
2157 ResolvedName.MaximumLength = ResolvedName.Length + sizeof(WCHAR);
2158 pName = &ResolvedName;
2159 }
2160
[66525]2161#ifndef IN_SUP_R3_STATIC
2162 /*
2163 * Reject blacklisted DLLs based on input name.
2164 */
2165 for (unsigned i = 0; g_aSupNtViBlacklistedDlls[i].psz != NULL; i++)
2166 if (supR3HardenedIsFilenameMatchDll(pName, g_aSupNtViBlacklistedDlls[i].psz, g_aSupNtViBlacklistedDlls[i].cch))
2167 {
2168 SUP_DPRINTF(("supR3HardenedMonitor_LdrLoadDll: Refusing to load blacklisted DLL: '%.*ls'\n",
2169 pName->Length / sizeof(RTUTF16), pName->Buffer));
2170 RtlRestoreLastWin32Error(dwSavedLastError);
2171 return STATUS_TOO_MANY_THREADS;
2172 }
2173#endif
2174
[53051]2175 bool fQuiet = false;
[52403]2176 if (!fSkipValidation)
2177 {
2178 /*
2179 * Try open the file. If this fails, never mind, just pass it on to
2180 * the real API as we've replaced any searchable name with a full name
2181 * and the real API can come up with a fitting status code for it.
2182 */
[52947]2183 HANDLE hRootDir;
2184 UNICODE_STRING NtPathUniStr;
2185 int rc = RTNtPathFromWinUtf16Ex(&NtPathUniStr, &hRootDir, wszPath, RTSTR_MAX);
2186 if (RT_FAILURE(rc))
[52403]2187 {
[52947]2188 supR3HardenedError(rc, false,
2189 "supR3HardenedMonitor_LdrLoadDll: RTNtPathFromWinUtf16Ex failed on '%ls': %Rrc\n", wszPath, rc);
2190 SUP_DPRINTF(("supR3HardenedMonitor_LdrLoadDll: returns rcNt=%#x\n", STATUS_OBJECT_NAME_INVALID));
2191 RtlRestoreLastWin32Error(dwSavedLastError);
2192 return STATUS_OBJECT_NAME_INVALID;
2193 }
2194
2195 HANDLE hFile = RTNT_INVALID_HANDLE_VALUE;
2196 IO_STATUS_BLOCK Ios = RTNT_IO_STATUS_BLOCK_INITIALIZER;
2197 OBJECT_ATTRIBUTES ObjAttr;
2198 InitializeObjectAttributes(&ObjAttr, &NtPathUniStr, OBJ_CASE_INSENSITIVE, hRootDir, NULL /*pSecDesc*/);
2199
2200 rcNt = NtCreateFile(&hFile,
2201 FILE_READ_DATA | READ_CONTROL | SYNCHRONIZE,
2202 &ObjAttr,
2203 &Ios,
2204 NULL /* Allocation Size*/,
2205 FILE_ATTRIBUTE_NORMAL,
2206 FILE_SHARE_READ,
2207 FILE_OPEN,
2208 FILE_NON_DIRECTORY_FILE | FILE_SYNCHRONOUS_IO_NONALERT,
2209 NULL /*EaBuffer*/,
2210 0 /*EaLength*/);
2211 if (NT_SUCCESS(rcNt))
2212 rcNt = Ios.Status;
2213 if (NT_SUCCESS(rcNt))
2214 {
[52403]2215 ULONG fAccess = 0;
2216 ULONG fProtect = 0;
2217 bool fCallRealApi = false;
[53220]2218 rcNt = supR3HardenedScreenImage(hFile, true /*fImage*/, RT_VALID_PTR(pfFlags) && (*pfFlags & 0x2) /*fIgnoreArch*/,
2219 &fAccess, &fProtect, &fCallRealApi,
[53045]2220 "LdrLoadDll", false /*fAvoidWinVerifyTrust*/, &fQuiet);
[52403]2221 NtClose(hFile);
2222 if (!NT_SUCCESS(rcNt))
2223 {
[53045]2224 if (!fQuiet)
[52528]2225 {
[53051]2226 if (pOrgName != pName)
2227 supR3HardenedError(VINF_SUCCESS, false, "supR3HardenedMonitor_LdrLoadDll: rejecting '%ls': rcNt=%#x\n",
2228 wszPath, rcNt);
2229 else
2230 supR3HardenedError(VINF_SUCCESS, false, "supR3HardenedMonitor_LdrLoadDll: rejecting '%ls' (%.*ls): rcNt=%#x\n",
2231 wszPath, pOrgName->Length / sizeof(WCHAR), pOrgName->Buffer, rcNt);
[52528]2232 SUP_DPRINTF(("supR3HardenedMonitor_LdrLoadDll: returns rcNt=%#x '%ls'\n", rcNt, wszPath));
2233 }
[52940]2234 RtlRestoreLastWin32Error(dwSavedLastError);
[52403]2235 return rcNt;
2236 }
2237
2238 supR3HardenedWinVerifyCacheProcessImportTodos();
2239 }
2240 else
2241 {
[52940]2242 DWORD dwErr = RtlGetLastWin32Error();
[53821]2243
2244 /*
2245 * Deal with special case where the caller (first case was MS LifeCam)
2246 * is using LoadLibrary instead of GetModuleHandle to find a loaded DLL.
2247 */
2248 NTSTATUS rcNtGetDll = STATUS_SUCCESS;
2249 if ( fCheckIfLoaded
2250 && ( rcNt == STATUS_OBJECT_NAME_NOT_FOUND
2251 || rcNt == STATUS_OBJECT_PATH_NOT_FOUND))
2252 {
2253 rcNtGetDll = LdrGetDllHandle(NULL /*DllPath*/, NULL /*pfFlags*/, pOrgName, phMod);
2254 if (NT_SUCCESS(rcNtGetDll))
2255 {
[67968]2256 RTNtPathFree(&NtPathUniStr, &hRootDir);
[53821]2257 RtlRestoreLastWin32Error(dwSavedLastError);
2258 return rcNtGetDll;
2259 }
2260 }
2261
2262 SUP_DPRINTF(("supR3HardenedMonitor_LdrLoadDll: error opening '%ls': %u (NtPath=%.*ls; Input=%.*ls; rcNtGetDll=%#x\n",
[53051]2263 wszPath, dwErr, NtPathUniStr.Length / sizeof(RTUTF16), NtPathUniStr.Buffer,
[53821]2264 pOrgName->Length / sizeof(WCHAR), pOrgName->Buffer, rcNtGetDll));
[67968]2265
2266 RTNtPathFree(&NtPathUniStr, &hRootDir);
2267 RtlRestoreLastWin32Error(dwSavedLastError);
2268 SUP_DPRINTF(("supR3HardenedMonitor_LdrLoadDll: returns rcNt=%#x '%ls'\n", rcNt, wszPath));
2269 return rcNt;
[52403]2270 }
[52947]2271 RTNtPathFree(&NtPathUniStr, &hRootDir);
[52403]2272 }
2273
2274 /*
2275 * Screened successfully enough. Call the real thing.
2276 */
[53045]2277 if (!fQuiet)
[53051]2278 {
2279 if (pOrgName != pName)
2280 SUP_DPRINTF(("supR3HardenedMonitor_LdrLoadDll: pName=%.*ls (Input=%.*ls, rcNtResolve=%#x) *pfFlags=%#x pwszSearchPath=%p:%ls [calling]\n",
2281 (unsigned)pName->Length / sizeof(WCHAR), pName->Buffer,
2282 (unsigned)pOrgName->Length / sizeof(WCHAR), pOrgName->Buffer, rcNtResolve,
2283 pfFlags ? *pfFlags : UINT32_MAX, pwszSearchPath,
2284 !((uintptr_t)pwszSearchPath & 1) && (uintptr_t)pwszSearchPath >= 0x2000U ? pwszSearchPath : L"<flags>"));
2285 else
2286 SUP_DPRINTF(("supR3HardenedMonitor_LdrLoadDll: pName=%.*ls (rcNtResolve=%#x) *pfFlags=%#x pwszSearchPath=%p:%ls [calling]\n",
2287 (unsigned)pName->Length / sizeof(WCHAR), pName->Buffer, rcNtResolve,
2288 pfFlags ? *pfFlags : UINT32_MAX, pwszSearchPath,
2289 !((uintptr_t)pwszSearchPath & 1) && (uintptr_t)pwszSearchPath >= 0x2000U ? pwszSearchPath : L"<flags>"));
2290 }
2291
[52940]2292 RtlRestoreLastWin32Error(dwSavedLastError);
[52403]2293 rcNt = g_pfnLdrLoadDllReal(pwszSearchPath, pfFlags, pName, phMod);
2294
[52484]2295 /*
2296 * Log the result and process pending WinVerifyTrust work if we can.
2297 */
[52940]2298 dwSavedLastError = RtlGetLastWin32Error();
[52484]2299
[52403]2300 if (NT_SUCCESS(rcNt) && phMod)
2301 SUP_DPRINTF(("supR3HardenedMonitor_LdrLoadDll: returns rcNt=%#x hMod=%p '%ls'\n", rcNt, *phMod, wszPath));
[53045]2302 else if (!NT_SUCCESS(rcNt) || !fQuiet)
[52403]2303 SUP_DPRINTF(("supR3HardenedMonitor_LdrLoadDll: returns rcNt=%#x '%ls'\n", rcNt, wszPath));
[52967]2304
[52431]2305 supR3HardenedWinVerifyCacheProcessWvtTodos();
[52484]2306
[52940]2307 RtlRestoreLastWin32Error(dwSavedLastError);
[52403]2308
2309 return rcNt;
2310}
2311
2312
[52953]2313/**
2314 * DLL load and unload notification callback.
2315 *
2316 * This is a safety against our LdrLoadDll hook being replaced by protection
2317 * software. Though, we prefer the LdrLoadDll hook to this one as it allows us
2318 * to call WinVerifyTrust more freely.
2319 *
2320 * @param ulReason The reason we're called, see
2321 * LDR_DLL_NOTIFICATION_REASON_XXX.
2322 * @param pData Reason specific data. (Format is currently the same for
2323 * both load and unload.)
2324 * @param pvUser User parameter (ignored).
2325 *
2326 * @remarks Vista and later.
2327 * @remarks The loader lock is held when we're called, at least on Windows 7.
2328 */
2329static VOID CALLBACK supR3HardenedDllNotificationCallback(ULONG ulReason, PCLDR_DLL_NOTIFICATION_DATA pData, PVOID pvUser)
2330{
2331 NOREF(pvUser);
2332
2333 /*
2334 * Screen the image on load. We will normally get a verification cache
2335 * hit here because of the LdrLoadDll and NtCreateSection hooks, so it
2336 * should be relatively cheap to recheck. In case our NtDll patches
2337 * got re
2338 *
2339 * This ASSUMES that we get informed after the fact as indicated by the
2340 * available documentation.
2341 */
2342 if (ulReason == LDR_DLL_NOTIFICATION_REASON_LOADED)
2343 {
2344 SUP_DPRINTF(("supR3HardenedDllNotificationCallback: load %p LB %#010x %.*ls [fFlags=%#x]\n",
2345 pData->Loaded.DllBase, pData->Loaded.SizeOfImage,
2346 pData->Loaded.FullDllName->Length / sizeof(WCHAR), pData->Loaded.FullDllName->Buffer,
2347 pData->Loaded.Flags));
2348
2349 /* Convert the windows path to an NT path and open it. */
2350 HANDLE hRootDir;
2351 UNICODE_STRING NtPathUniStr;
2352 int rc = RTNtPathFromWinUtf16Ex(&NtPathUniStr, &hRootDir, pData->Loaded.FullDllName->Buffer,
2353 pData->Loaded.FullDllName->Length / sizeof(WCHAR));
2354 if (RT_FAILURE(rc))
2355 {
2356 supR3HardenedFatal("supR3HardenedDllNotificationCallback: RTNtPathFromWinUtf16Ex failed on '%.*ls': %Rrc\n",
2357 pData->Loaded.FullDllName->Length / sizeof(WCHAR), pData->Loaded.FullDllName->Buffer, rc);
2358 return;
2359 }
2360
2361 HANDLE hFile = RTNT_INVALID_HANDLE_VALUE;
2362 IO_STATUS_BLOCK Ios = RTNT_IO_STATUS_BLOCK_INITIALIZER;
2363 OBJECT_ATTRIBUTES ObjAttr;
2364 InitializeObjectAttributes(&ObjAttr, &NtPathUniStr, OBJ_CASE_INSENSITIVE, hRootDir, NULL /*pSecDesc*/);
2365
2366 NTSTATUS rcNt = NtCreateFile(&hFile,
2367 FILE_READ_DATA | READ_CONTROL | SYNCHRONIZE,
2368 &ObjAttr,
2369 &Ios,
2370 NULL /* Allocation Size*/,
2371 FILE_ATTRIBUTE_NORMAL,
2372 FILE_SHARE_READ,
2373 FILE_OPEN,
2374 FILE_NON_DIRECTORY_FILE | FILE_SYNCHRONOUS_IO_NONALERT,
2375 NULL /*EaBuffer*/,
2376 0 /*EaLength*/);
2377 if (NT_SUCCESS(rcNt))
2378 rcNt = Ios.Status;
2379 if (!NT_SUCCESS(rcNt))
2380 {
2381 supR3HardenedFatal("supR3HardenedDllNotificationCallback: NtCreateFile failed on '%.*ls' / '%.*ls': %#x\n",
2382 pData->Loaded.FullDllName->Length / sizeof(WCHAR), pData->Loaded.FullDllName->Buffer,
2383 NtPathUniStr.Length / sizeof(WCHAR), NtPathUniStr.Buffer, rcNt);
[62677]2384 /* not reached */
[52953]2385 }
2386
2387 /* Do the screening. */
2388 ULONG fAccess = 0;
2389 ULONG fProtect = 0;
2390 bool fCallRealApi = false;
2391 bool fQuietFailure = false;
[53220]2392 rcNt = supR3HardenedScreenImage(hFile, true /*fImage*/, true /*fIgnoreArch*/, &fAccess, &fProtect, &fCallRealApi,
[52953]2393 "LdrLoadDll", true /*fAvoidWinVerifyTrust*/, &fQuietFailure);
2394 NtClose(hFile);
2395 if (!NT_SUCCESS(rcNt))
2396 {
2397 supR3HardenedFatal("supR3HardenedDllNotificationCallback: supR3HardenedScreenImage failed on '%.*ls' / '%.*ls': %#x\n",
2398 pData->Loaded.FullDllName->Length / sizeof(WCHAR), pData->Loaded.FullDllName->Buffer,
2399 NtPathUniStr.Length / sizeof(WCHAR), NtPathUniStr.Buffer, rcNt);
[62677]2400 /* not reached */
[52953]2401 }
2402 RTNtPathFree(&NtPathUniStr, &hRootDir);
2403 }
2404 /*
2405 * Log the unload call.
2406 */
2407 else if (ulReason == LDR_DLL_NOTIFICATION_REASON_UNLOADED)
2408 {
2409 SUP_DPRINTF(("supR3HardenedDllNotificationCallback: Unload %p LB %#010x %.*ls [flags=%#x]\n",
2410 pData->Unloaded.DllBase, pData->Unloaded.SizeOfImage,
2411 pData->Unloaded.FullDllName->Length / sizeof(WCHAR), pData->Unloaded.FullDllName->Buffer,
2412 pData->Unloaded.Flags));
2413 }
2414 /*
2415 * Just log things we don't know and then return without caching anything.
2416 */
2417 else
2418 {
2419 static uint32_t s_cLogEntries = 0;
2420 if (s_cLogEntries++ < 32)
2421 SUP_DPRINTF(("supR3HardenedDllNotificationCallback: ulReason=%u pData=%p\n", ulReason, pData));
2422 return;
2423 }
2424
2425 /*
2426 * Use this opportunity to make sure our NtDll patches are still in place,
2427 * since they may be replaced by indecent protection software solutions.
2428 */
2429 supR3HardenedWinReInstallHooks(false /*fFirstCall */);
2430}
2431
2432
2433/**
2434 * Registers the DLL notification callback if it hasn't already been registered.
2435 */
2436static void supR3HardenedWinRegisterDllNotificationCallback(void)
2437{
2438 /*
2439 * The notification API was added in Vista, so it's an optional (weak) import.
2440 */
2441 if ( LdrRegisterDllNotification != NULL
2442 && g_cDllNotificationRegistered <= 0
2443 && g_cDllNotificationRegistered > -32)
2444 {
2445 NTSTATUS rcNt = LdrRegisterDllNotification(0, supR3HardenedDllNotificationCallback, NULL, &g_pvDllNotificationCookie);
2446 if (NT_SUCCESS(rcNt))
2447 {
2448 SUP_DPRINTF(("Registered Dll notification callback with NTDLL.\n"));
2449 g_cDllNotificationRegistered = 1;
2450 }
2451 else
2452 {
2453 supR3HardenedError(rcNt, false /*fFatal*/, "LdrRegisterDllNotification failed: %#x\n", rcNt);
2454 g_cDllNotificationRegistered--;
2455 }
2456 }
2457}
2458
2459
[52403]2460static void supR3HardenedWinHookFailed(const char *pszWhich, uint8_t const *pbPrologue)
2461{
2462 supR3HardenedFatalMsg("supR3HardenedWinInstallHooks", kSupInitOp_Misc, VERR_NO_MEMORY,
2463 "Failed to install %s monitor: %x %x %x %x %x %x %x %x %x %x %x %x %x %x %x %x\n "
2464#ifdef RT_ARCH_X86
2465 "(It is also possible you are running 32-bit VirtualBox under 64-bit windows.)\n"
2466#endif
2467 ,
2468 pszWhich,
2469 pbPrologue[0], pbPrologue[1], pbPrologue[2], pbPrologue[3],
2470 pbPrologue[4], pbPrologue[5], pbPrologue[6], pbPrologue[7],
2471 pbPrologue[8], pbPrologue[9], pbPrologue[10], pbPrologue[11],
2472 pbPrologue[12], pbPrologue[13], pbPrologue[14], pbPrologue[15]);
2473}
2474
2475
[51770]2476/**
[52632]2477 * IPRT thread that waits for the parent process to terminate and reacts by
2478 * exiting the current process.
2479 *
2480 * @returns VINF_SUCCESS
2481 * @param hSelf The current thread. Ignored.
2482 * @param pvUser The handle of the parent process.
2483 */
2484static DECLCALLBACK(int) supR3HardenedWinParentWatcherThread(RTTHREAD hSelf, void *pvUser)
2485{
2486 HANDLE hProcWait = (HANDLE)pvUser;
2487 NOREF(hSelf);
2488
2489 /*
2490 * Wait for the parent to terminate.
2491 */
2492 NTSTATUS rcNt;
2493 for (;;)
2494 {
2495 rcNt = NtWaitForSingleObject(hProcWait, TRUE /*Alertable*/, NULL /*pTimeout*/);
2496 if ( rcNt == STATUS_WAIT_0
2497 || rcNt == STATUS_ABANDONED_WAIT_0)
2498 break;
2499 if ( rcNt != STATUS_TIMEOUT
2500 && rcNt != STATUS_USER_APC
2501 && rcNt != STATUS_ALERTED)
2502 supR3HardenedFatal("NtWaitForSingleObject returned %#x\n", rcNt);
2503 }
2504
2505 /*
2506 * Proxy the termination code of the child, if it exited already.
2507 */
2508 PROCESS_BASIC_INFORMATION BasicInfo;
2509 NTSTATUS rcNt2 = NtQueryInformationProcess(hProcWait, ProcessBasicInformation, &BasicInfo, sizeof(BasicInfo), NULL);
2510 if ( !NT_SUCCESS(rcNt2)
2511 || BasicInfo.ExitStatus == STATUS_PENDING)
2512 BasicInfo.ExitStatus = RTEXITCODE_FAILURE;
2513
2514 NtClose(hProcWait);
2515 SUP_DPRINTF(("supR3HardenedWinParentWatcherThread: Quitting: ExitCode=%#x rcNt=%#x\n", BasicInfo.ExitStatus, rcNt));
2516 suplibHardenedExit((RTEXITCODE)BasicInfo.ExitStatus);
[62677]2517 /* not reached */
[52632]2518}
2519
2520
2521/**
2522 * Creates the parent watcher thread that will make sure this process exits when
2523 * the parent does.
2524 *
2525 * This is a necessary evil to make VBoxNetDhcp and VBoxNetNat termination from
2526 * Main work without too much new magic. It also makes Ctrl-C or similar work
2527 * in on the hardened processes in the windows console.
2528 *
2529 * @param hVBoxRT The VBoxRT.dll handle. We use RTThreadCreate to
2530 * spawn the thread to avoid duplicating thread
2531 * creation and thread naming code from IPRT.
2532 */
2533DECLHIDDEN(void) supR3HardenedWinCreateParentWatcherThread(HMODULE hVBoxRT)
2534{
2535 /*
2536 * Resolve runtime methods that we need.
2537 */
2538 PFNRTTHREADCREATE pfnRTThreadCreate = (PFNRTTHREADCREATE)GetProcAddress(hVBoxRT, "RTThreadCreate");
2539 SUPR3HARDENED_ASSERT(pfnRTThreadCreate != NULL);
2540
2541 /*
2542 * Find the parent process ID.
2543 */
2544 PROCESS_BASIC_INFORMATION BasicInfo;
2545 NTSTATUS rcNt = NtQueryInformationProcess(NtCurrentProcess(), ProcessBasicInformation, &BasicInfo, sizeof(BasicInfo), NULL);
2546 if (!NT_SUCCESS(rcNt))
2547 supR3HardenedFatal("supR3HardenedWinCreateParentWatcherThread: NtQueryInformationProcess failed: %#x\n", rcNt);
2548
2549 /*
2550 * Open the parent process for waiting and exitcode query.
2551 */
2552 OBJECT_ATTRIBUTES ObjAttr;
2553 InitializeObjectAttributes(&ObjAttr, NULL, 0, NULL /*hRootDir*/, NULL /*pSecDesc*/);
2554
2555 CLIENT_ID ClientId;
2556 ClientId.UniqueProcess = (HANDLE)BasicInfo.InheritedFromUniqueProcessId;
2557 ClientId.UniqueThread = NULL;
[52633]2558
[52632]2559 HANDLE hParent;
2560 rcNt = NtOpenProcess(&hParent, SYNCHRONIZE | PROCESS_QUERY_INFORMATION, &ObjAttr, &ClientId);
2561 if (!NT_SUCCESS(rcNt))
[52633]2562 supR3HardenedFatalMsg("supR3HardenedWinCreateParentWatcherThread", kSupInitOp_Misc, VERR_GENERAL_FAILURE,
[52632]2563 "NtOpenProcess(%p.0) failed: %#x\n", ClientId.UniqueProcess, rcNt);
2564
2565 /*
2566 * Create the thread that should do the waiting.
2567 */
2568 int rc = pfnRTThreadCreate(NULL, supR3HardenedWinParentWatcherThread, hParent, _64K /* stack */,
2569 RTTHREADTYPE_DEFAULT, 0 /*fFlags*/, "ParentWatcher");
2570 if (RT_FAILURE(rc))
2571 supR3HardenedFatal("supR3HardenedWinCreateParentWatcherThread: RTThreadCreate failed: %Rrc\n", rc);
2572}
2573
2574
2575/**
[52954]2576 * Checks if the calling thread is the only one in the process.
2577 *
2578 * @returns true if we're positive we're alone, false if not.
2579 */
2580static bool supR3HardenedWinAmIAlone(void)
2581{
2582 ULONG fAmIAlone = 0;
2583 ULONG cbIgn = 0;
2584 NTSTATUS rcNt = NtQueryInformationThread(NtCurrentThread(), ThreadAmILastThread, &fAmIAlone, sizeof(fAmIAlone), &cbIgn);
2585 Assert(NT_SUCCESS(rcNt));
2586 return NT_SUCCESS(rcNt) && fAmIAlone != 0;
2587}
2588
2589
2590/**
[52940]2591 * Simplify NtProtectVirtualMemory interface.
2592 *
2593 * Modifies protection for the current process. Caller must know the current
2594 * protection as it's not returned.
2595 *
2596 * @returns NT status code.
2597 * @param pvMem The memory to change protection for.
2598 * @param cbMem The amount of memory to change.
2599 * @param fNewProt The new protection.
2600 */
2601static NTSTATUS supR3HardenedWinProtectMemory(PVOID pvMem, SIZE_T cbMem, ULONG fNewProt)
2602{
2603 ULONG fOldProt = 0;
2604 return NtProtectVirtualMemory(NtCurrentProcess(), &pvMem, &cbMem, fNewProt, &fOldProt);
2605}
2606
2607
2608/**
[52953]2609 * Installs or reinstalls the NTDLL patches.
2610 */
2611static void supR3HardenedWinReInstallHooks(bool fFirstCall)
2612{
2613 struct
2614 {
2615 size_t cbPatch;
2616 uint8_t const *pabPatch;
2617 uint8_t **ppbApi;
2618 const char *pszName;
2619 } const s_aPatches[] =
2620 {
2621 { sizeof(g_abNtCreateSectionPatch), g_abNtCreateSectionPatch, &g_pbNtCreateSection, "NtCreateSection" },
2622 { sizeof(g_abLdrLoadDllPatch), g_abLdrLoadDllPatch, &g_pbLdrLoadDll, "LdrLoadDll" },
2623 };
2624
2625 ULONG fAmIAlone = ~(ULONG)0;
2626
2627 for (uint32_t i = 0; i < RT_ELEMENTS(s_aPatches); i++)
2628 {
2629 uint8_t *pbApi = *s_aPatches[i].ppbApi;
2630 if (memcmp(pbApi, s_aPatches[i].pabPatch, s_aPatches[i].cbPatch) != 0)
2631 {
2632 /*
2633 * Log the incident if it's not the initial call.
2634 */
2635 static uint32_t volatile s_cTimes = 0;
2636 if (!fFirstCall && s_cTimes < 128)
2637 {
2638 s_cTimes++;
2639 SUP_DPRINTF(("supR3HardenedWinReInstallHooks: Reinstalling %s (%p: %.*Rhxs).\n",
2640 s_aPatches[i].pszName, pbApi, s_aPatches[i].cbPatch, pbApi));
2641 }
2642
2643 Assert(s_aPatches[i].cbPatch >= 4);
2644
2645 SUPR3HARDENED_ASSERT_NT_SUCCESS(supR3HardenedWinProtectMemory(pbApi, s_aPatches[i].cbPatch, PAGE_EXECUTE_READWRITE));
2646
2647 /*
2648 * If we're alone, just memcpy the patch in.
2649 */
2650
2651 if (fAmIAlone == ~(ULONG)0)
[52954]2652 fAmIAlone = supR3HardenedWinAmIAlone();
[52953]2653 if (fAmIAlone)
2654 memcpy(pbApi, s_aPatches[i].pabPatch, s_aPatches[i].cbPatch);
2655 else
2656 {
2657 /*
2658 * Not alone. Start by injecting a JMP $-2, then waste some
2659 * CPU cycles to get the other threads a good chance of getting
2660 * out of the code before we replace it.
2661 */
2662 RTUINT32U uJmpDollarMinus;
2663 uJmpDollarMinus.au8[0] = 0xeb;
2664 uJmpDollarMinus.au8[1] = 0xfe;
2665 uJmpDollarMinus.au8[2] = pbApi[2];
2666 uJmpDollarMinus.au8[3] = pbApi[3];
2667 ASMAtomicXchgU32((uint32_t volatile *)pbApi, uJmpDollarMinus.u);
2668
2669 NtYieldExecution();
2670 NtYieldExecution();
2671
2672 /* Copy in the tail bytes of the patch, then xchg the jmp $-2. */
2673 if (s_aPatches[i].cbPatch > 4)
2674 memcpy(&pbApi[4], &s_aPatches[i].pabPatch[4], s_aPatches[i].cbPatch - 4);
2675 ASMAtomicXchgU32((uint32_t volatile *)pbApi, *(uint32_t *)s_aPatches[i].pabPatch);
2676 }
2677
2678 SUPR3HARDENED_ASSERT_NT_SUCCESS(supR3HardenedWinProtectMemory(pbApi, s_aPatches[i].cbPatch, PAGE_EXECUTE_READ));
2679 }
2680 }
2681}
2682
2683
2684/**
[51770]2685 * Install hooks for intercepting calls dealing with mapping shared libraries
2686 * into the process.
2687 *
2688 * This allows us to prevent undesirable shared libraries from being loaded.
2689 *
2690 * @remarks We assume we're alone in this process, so no seralizing trickery is
2691 * necessary when installing the patch.
[52403]2692 *
2693 * @remarks We would normally just copy the prologue sequence somewhere and add
2694 * a jump back at the end of it. But because we wish to avoid
2695 * allocating executable memory, we need to have preprepared assembly
2696 * "copies". This makes the non-system call patching a little tedious
2697 * and inflexible.
[51770]2698 */
[52953]2699static void supR3HardenedWinInstallHooks(void)
[51770]2700{
[52092]2701 NTSTATUS rcNt;
2702
[51770]2703 /*
[52092]2704 * Disable hard error popups so we can quietly refuse images to be loaded.
2705 */
2706 ULONG fHardErr = 0;
2707 rcNt = NtQueryInformationProcess(NtCurrentProcess(), ProcessDefaultHardErrorMode, &fHardErr, sizeof(fHardErr), NULL);
2708 if (!NT_SUCCESS(rcNt))
2709 supR3HardenedFatalMsg("supR3HardenedWinInstallHooks", kSupInitOp_Misc, VERR_GENERAL_FAILURE,
2710 "NtQueryInformationProcess/ProcessDefaultHardErrorMode failed: %#x\n", rcNt);
2711 if (fHardErr & PROCESS_HARDERR_CRITICAL_ERROR)
2712 {
2713 fHardErr &= ~PROCESS_HARDERR_CRITICAL_ERROR;
2714 rcNt = NtSetInformationProcess(NtCurrentProcess(), ProcessDefaultHardErrorMode, &fHardErr, sizeof(fHardErr));
2715 if (!NT_SUCCESS(rcNt))
2716 supR3HardenedFatalMsg("supR3HardenedWinInstallHooks", kSupInitOp_Misc, VERR_GENERAL_FAILURE,
2717 "NtSetInformationProcess/ProcessDefaultHardErrorMode failed: %#x\n", rcNt);
2718 }
2719
2720 /*
[52403]2721 * Locate the routines first so we can allocate memory that's near enough.
[51770]2722 */
[52795]2723 PFNRT pfnNtCreateSection = supR3HardenedWinGetRealDllSymbol("ntdll.dll", "NtCreateSection");
[51770]2724 SUPR3HARDENED_ASSERT(pfnNtCreateSection != NULL);
[52403]2725 //SUPR3HARDENED_ASSERT(pfnNtCreateSection == (FARPROC)NtCreateSection);
[51770]2726
[52795]2727 PFNRT pfnLdrLoadDll = supR3HardenedWinGetRealDllSymbol("ntdll.dll", "LdrLoadDll");
[52403]2728 SUPR3HARDENED_ASSERT(pfnLdrLoadDll != NULL);
2729 //SUPR3HARDENED_ASSERT(pfnLdrLoadDll == (FARPROC)LdrLoadDll);
[51770]2730
2731 /*
[52967]2732 * Exec page setup & management.
[51770]2733 */
[52967]2734 uint32_t offExecPage = 0;
2735 memset(g_abSupHardReadWriteExecPage, 0xcc, PAGE_SIZE);
[51770]2736
2737 /*
[52403]2738 * Hook #1 - NtCreateSection.
2739 * Purpose: Validate everything that can be mapped into the process before
2740 * it's mapped and we still have a file handle to work with.
2741 */
2742 uint8_t * const pbNtCreateSection = (uint8_t *)(uintptr_t)pfnNtCreateSection;
[52953]2743 g_pbNtCreateSection = pbNtCreateSection;
2744 memcpy(g_abNtCreateSectionPatch, pbNtCreateSection, sizeof(g_abNtCreateSectionPatch));
[52403]2745
[52967]2746 g_pfnNtCreateSectionReal = NtCreateSection; /* our direct syscall */
2747
[52403]2748#ifdef RT_ARCH_AMD64
2749 /*
[51770]2750 * Patch 64-bit hosts.
2751 */
2752 /* Pattern #1: XP64/W2K3-64 thru Windows 8.1
2753 0:000> u ntdll!NtCreateSection
2754 ntdll!NtCreateSection:
2755 00000000`779f1750 4c8bd1 mov r10,rcx
2756 00000000`779f1753 b847000000 mov eax,47h
2757 00000000`779f1758 0f05 syscall
2758 00000000`779f175a c3 ret
2759 00000000`779f175b 0f1f440000 nop dword ptr [rax+rax]
2760 The variant is the value loaded into eax: W2K3=??, Vista=47h?, W7=47h, W80=48h, W81=49h */
2761
[52953]2762 /* Assemble the patch. */
[52967]2763 g_abNtCreateSectionPatch[0] = 0x48; /* mov rax, qword */
2764 g_abNtCreateSectionPatch[1] = 0xb8;
2765 *(uint64_t *)&g_abNtCreateSectionPatch[2] = (uint64_t)supR3HardenedMonitor_NtCreateSection;
2766 g_abNtCreateSectionPatch[10] = 0xff; /* jmp rax */
2767 g_abNtCreateSectionPatch[11] = 0xe0;
[52940]2768
[51770]2769#else
2770 /*
2771 * Patch 32-bit hosts.
2772 */
2773 /* Pattern #1: XP thru Windows 7
2774 kd> u ntdll!NtCreateSection
2775 ntdll!NtCreateSection:
2776 7c90d160 b832000000 mov eax,32h
2777 7c90d165 ba0003fe7f mov edx,offset SharedUserData!SystemCallStub (7ffe0300)
2778 7c90d16a ff12 call dword ptr [edx]
2779 7c90d16c c21c00 ret 1Ch
2780 7c90d16f 90 nop
2781 The variable bit is the value loaded into eax: XP=32h, W2K3=34h, Vista=4bh, W7=54h
2782
2783 Pattern #2: Windows 8.1
2784 0:000:x86> u ntdll_6a0f0000!NtCreateSection
2785 ntdll_6a0f0000!NtCreateSection:
2786 6a15eabc b854010000 mov eax,154h
2787 6a15eac1 e803000000 call ntdll_6a0f0000!NtCreateSection+0xd (6a15eac9)
2788 6a15eac6 c21c00 ret 1Ch
2789 6a15eac9 8bd4 mov edx,esp
2790 6a15eacb 0f34 sysenter
2791 6a15eacd c3 ret
[52967]2792 The variable bit is the value loaded into eax: W81=154h */
[51770]2793
[52953]2794 /* Assemble the patch. */
[52967]2795 g_abNtCreateSectionPatch[0] = 0xe9; /* jmp rel32 */
[52953]2796 *(uint32_t *)&g_abNtCreateSectionPatch[1] = (uintptr_t)supR3HardenedMonitor_NtCreateSection
2797 - (uintptr_t)&pbNtCreateSection[1+4];
[52967]2798
[52403]2799#endif
2800
2801 /*
2802 * Hook #2 - LdrLoadDll
2803 * Purpose: (a) Enforce LdrLoadDll search path constraints, and (b) pre-validate
2804 * DLLs so we can avoid calling WinVerifyTrust from the first hook,
2805 * and thus avoiding messing up the loader data on some installations.
2806 *
2807 * This differs from the above function in that is no a system call and
2808 * we're at the mercy of the compiler.
2809 */
2810 uint8_t * const pbLdrLoadDll = (uint8_t *)(uintptr_t)pfnLdrLoadDll;
[52953]2811 g_pbLdrLoadDll = pbLdrLoadDll;
2812 memcpy(g_abLdrLoadDllPatch, pbLdrLoadDll, sizeof(g_abLdrLoadDllPatch));
[52403]2813
[52967]2814 DISSTATE Dis;
2815 uint32_t cbInstr;
2816 uint32_t offJmpBack = 0;
2817
[52403]2818#ifdef RT_ARCH_AMD64
2819 /*
2820 * Patch 64-bit hosts.
2821 */
[52967]2822 /* Just use the disassembler to skip 12 bytes or more. */
2823 while (offJmpBack < 12)
[52403]2824 {
2825 cbInstr = 1;
2826 int rc = DISInstr(pbLdrLoadDll + offJmpBack, DISCPUMODE_64BIT, &Dis, &cbInstr);
2827 if ( RT_FAILURE(rc)
2828 || (Dis.pCurInstr->fOpType & (DISOPTYPE_CONTROLFLOW))
2829 || (Dis.ModRM.Bits.Mod == 0 && Dis.ModRM.Bits.Rm == 5 /* wrt RIP */) )
2830 supR3HardenedWinHookFailed("LdrLoadDll", pbLdrLoadDll);
2831 offJmpBack += cbInstr;
2832 }
2833
2834 /* Assemble the code for resuming the call.*/
2835 *(PFNRT *)&g_pfnLdrLoadDllReal = (PFNRT)(uintptr_t)&g_abSupHardReadWriteExecPage[offExecPage];
2836
2837 memcpy(&g_abSupHardReadWriteExecPage[offExecPage], pbLdrLoadDll, offJmpBack);
2838 offExecPage += offJmpBack;
2839
2840 g_abSupHardReadWriteExecPage[offExecPage++] = 0xff; /* jmp qword [$+8 wrt RIP] */
2841 g_abSupHardReadWriteExecPage[offExecPage++] = 0x25;
2842 *(uint32_t *)&g_abSupHardReadWriteExecPage[offExecPage] = RT_ALIGN_32(offExecPage + 4, 8) - (offExecPage + 4);
2843 offExecPage = RT_ALIGN_32(offExecPage + 4, 8);
2844 *(uint64_t *)&g_abSupHardReadWriteExecPage[offExecPage] = (uintptr_t)&pbLdrLoadDll[offJmpBack];
[65782]2845 offExecPage = RT_ALIGN_32(offExecPage + 8, 16);
[52403]2846
[52953]2847 /* Assemble the LdrLoadDll patch. */
[52967]2848 Assert(offJmpBack >= 12);
2849 g_abLdrLoadDllPatch[0] = 0x48; /* mov rax, qword */
2850 g_abLdrLoadDllPatch[1] = 0xb8;
2851 *(uint64_t *)&g_abLdrLoadDllPatch[2] = (uint64_t)supR3HardenedMonitor_LdrLoadDll;
2852 g_abLdrLoadDllPatch[10] = 0xff; /* jmp rax */
2853 g_abLdrLoadDllPatch[11] = 0xe0;
[52403]2854
2855#else
2856 /*
2857 * Patch 32-bit hosts.
2858 */
[52967]2859 /* Just use the disassembler to skip 5 bytes or more. */
[52403]2860 while (offJmpBack < 5)
2861 {
2862 cbInstr = 1;
2863 int rc = DISInstr(pbLdrLoadDll + offJmpBack, DISCPUMODE_32BIT, &Dis, &cbInstr);
2864 if ( RT_FAILURE(rc)
2865 || (Dis.pCurInstr->fOpType & (DISOPTYPE_CONTROLFLOW)) )
2866 supR3HardenedWinHookFailed("LdrLoadDll", pbLdrLoadDll);
2867 offJmpBack += cbInstr;
2868 }
2869
2870 /* Assemble the code for resuming the call.*/
2871 *(PFNRT *)&g_pfnLdrLoadDllReal = (PFNRT)(uintptr_t)&g_abSupHardReadWriteExecPage[offExecPage];
2872
2873 memcpy(&g_abSupHardReadWriteExecPage[offExecPage], pbLdrLoadDll, offJmpBack);
2874 offExecPage += offJmpBack;
2875
[52967]2876 g_abSupHardReadWriteExecPage[offExecPage++] = 0xe9; /* jmp rel32 */
[52403]2877 *(uint32_t *)&g_abSupHardReadWriteExecPage[offExecPage] = (uintptr_t)&pbLdrLoadDll[offJmpBack]
2878 - (uintptr_t)&g_abSupHardReadWriteExecPage[offExecPage + 4];
[65782]2879 offExecPage = RT_ALIGN_32(offExecPage + 4, 16);
[52403]2880
[52953]2881 /* Assemble the LdrLoadDll patch. */
2882 memcpy(g_abLdrLoadDllPatch, pbLdrLoadDll, sizeof(g_abLdrLoadDllPatch));
[52403]2883 Assert(offJmpBack >= 5);
[52953]2884 g_abLdrLoadDllPatch[0] = 0xe9;
2885 *(uint32_t *)&g_abLdrLoadDllPatch[1] = (uintptr_t)supR3HardenedMonitor_LdrLoadDll - (uintptr_t)&pbLdrLoadDll[1+4];
[51770]2886#endif
2887
[52403]2888 /*
2889 * Seal the rwx page.
2890 */
[52940]2891 SUPR3HARDENED_ASSERT_NT_SUCCESS(supR3HardenedWinProtectMemory(g_abSupHardReadWriteExecPage, PAGE_SIZE, PAGE_EXECUTE_READ));
[52953]2892
2893 /*
2894 * Install the patches.
2895 */
2896 supR3HardenedWinReInstallHooks(true /*fFirstCall*/);
[51770]2897}
2898
2899
[52969]2900
2901
2902
2903
2904/*
2905 *
2906 * T h r e a d c r e a t i o n c o n t r o l
2907 * T h r e a d c r e a t i o n c o n t r o l
2908 * T h r e a d c r e a t i o n c o n t r o l
2909 *
2910 */
2911
2912
[51770]2913/**
[52969]2914 * Common code used for child and parent to make new threads exit immediately.
2915 *
2916 * This patches the LdrInitializeThunk code to call NtTerminateThread with
2917 * STATUS_SUCCESS instead of doing the NTDLL initialization.
2918 *
2919 * @returns VBox status code.
2920 * @param hProcess The process to do this to.
2921 * @param pvLdrInitThunk The address of the LdrInitializeThunk code to
2922 * override.
2923 * @param pvNtTerminateThread The address of the NtTerminateThread function in
2924 * the NTDLL instance we're patching. (Must be +/-
2925 * 2GB from the thunk code.)
2926 * @param pabBackup Where to back up the original instruction bytes
2927 * at pvLdrInitThunk.
2928 * @param cbBackup The size of the backup area. Must be 16 bytes.
2929 * @param pErrInfo Where to return extended error information.
2930 * Optional.
2931 */
2932static int supR3HardNtDisableThreadCreationEx(HANDLE hProcess, void *pvLdrInitThunk, void *pvNtTerminateThread,
2933 uint8_t *pabBackup, size_t cbBackup, PRTERRINFO pErrInfo)
2934{
2935 SUP_DPRINTF(("supR3HardNtDisableThreadCreation: pvLdrInitThunk=%p pvNtTerminateThread=%p\n", pvLdrInitThunk, pvNtTerminateThread));
2936 SUPR3HARDENED_ASSERT(cbBackup == 16);
2937 SUPR3HARDENED_ASSERT(RT_ABS((intptr_t)pvLdrInitThunk - (intptr_t)pvNtTerminateThread) < 16*_1M);
2938
2939 /*
2940 * Back up the thunk code.
2941 */
2942 SIZE_T cbIgnored;
2943 NTSTATUS rcNt = NtReadVirtualMemory(hProcess, pvLdrInitThunk, pabBackup, cbBackup, &cbIgnored);
2944 if (!NT_SUCCESS(rcNt))
2945 return RTErrInfoSetF(pErrInfo, VERR_GENERAL_FAILURE,
2946 "supR3HardNtDisableThreadCreation: NtReadVirtualMemory/LdrInitializeThunk failed: %#x", rcNt);
2947
2948 /*
2949 * Cook up replacement code that calls NtTerminateThread.
2950 */
2951 uint8_t abReplacement[16];
2952 memcpy(abReplacement, pabBackup, sizeof(abReplacement));
2953
2954#ifdef RT_ARCH_AMD64
2955 abReplacement[0] = 0x31; /* xor ecx, ecx */
2956 abReplacement[1] = 0xc9;
2957 abReplacement[2] = 0x31; /* xor edx, edx */
2958 abReplacement[3] = 0xd2;
2959 abReplacement[4] = 0xe8; /* call near NtTerminateThread */
2960 *(int32_t *)&abReplacement[5] = (int32_t)((uintptr_t)pvNtTerminateThread - ((uintptr_t)pvLdrInitThunk + 9));
2961 abReplacement[9] = 0xcc; /* int3 */
2962#elif defined(RT_ARCH_X86)
2963 abReplacement[0] = 0x6a; /* push 0 */
2964 abReplacement[1] = 0x00;
2965 abReplacement[2] = 0x6a; /* push 0 */
2966 abReplacement[3] = 0x00;
2967 abReplacement[4] = 0xe8; /* call near NtTerminateThread */
2968 *(int32_t *)&abReplacement[5] = (int32_t)((uintptr_t)pvNtTerminateThread - ((uintptr_t)pvLdrInitThunk + 9));
2969 abReplacement[9] = 0xcc; /* int3 */
2970#else
2971# error "Unsupported arch."
2972#endif
2973
2974 /*
2975 * Install the replacment code.
2976 */
2977 PVOID pvProt = pvLdrInitThunk;
2978 SIZE_T cbProt = cbBackup;
2979 ULONG fOldProt = 0;
2980 rcNt = NtProtectVirtualMemory(hProcess, &pvProt, &cbProt, PAGE_EXECUTE_READWRITE, &fOldProt);
2981 if (!NT_SUCCESS(rcNt))
2982 return RTErrInfoSetF(pErrInfo, VERR_GENERAL_FAILURE,
2983 "supR3HardNtDisableThreadCreationEx: NtProtectVirtualMemory/LdrInitializeThunk failed: %#x", rcNt);
2984
2985 rcNt = NtWriteVirtualMemory(hProcess, pvLdrInitThunk, abReplacement, sizeof(abReplacement), &cbIgnored);
2986 if (!NT_SUCCESS(rcNt))
2987 return RTErrInfoSetF(pErrInfo, VERR_GENERAL_FAILURE,
2988 "supR3HardNtDisableThreadCreationEx: NtWriteVirtualMemory/LdrInitializeThunk failed: %#x", rcNt);
2989
2990 pvProt = pvLdrInitThunk;
2991 cbProt = cbBackup;
2992 rcNt = NtProtectVirtualMemory(hProcess, &pvProt, &cbProt, fOldProt, &fOldProt);
2993 if (!NT_SUCCESS(rcNt))
2994 return RTErrInfoSetF(pErrInfo, VERR_GENERAL_FAILURE,
2995 "supR3HardNtDisableThreadCreationEx: NtProtectVirtualMemory/LdrInitializeThunk/2 failed: %#x", rcNt);
2996
2997 return VINF_SUCCESS;
2998}
2999
3000
3001/**
3002 * Undo the effects of supR3HardNtDisableThreadCreationEx.
3003 *
3004 * @returns VBox status code.
3005 * @param hProcess The process to do this to.
3006 * @param pvLdrInitThunk The address of the LdrInitializeThunk code to
3007 * override.
3008 * @param pabBackup Where to back up the original instruction bytes
3009 * at pvLdrInitThunk.
3010 * @param cbBackup The size of the backup area. Must be 16 bytes.
3011 * @param pErrInfo Where to return extended error information.
3012 * Optional.
3013 */
3014static int supR3HardNtEnableThreadCreationEx(HANDLE hProcess, void *pvLdrInitThunk, uint8_t const *pabBackup, size_t cbBackup,
3015 PRTERRINFO pErrInfo)
3016{
3017 SUP_DPRINTF(("supR3HardNtEnableThreadCreation:\n"));
3018 SUPR3HARDENED_ASSERT(cbBackup == 16);
3019
3020 PVOID pvProt = pvLdrInitThunk;
3021 SIZE_T cbProt = cbBackup;
3022 ULONG fOldProt = 0;
3023 NTSTATUS rcNt = NtProtectVirtualMemory(hProcess, &pvProt, &cbProt, PAGE_EXECUTE_READWRITE, &fOldProt);
3024 if (!NT_SUCCESS(rcNt))
3025 return RTErrInfoSetF(pErrInfo, VERR_GENERAL_FAILURE,
3026 "supR3HardNtDisableThreadCreationEx: NtProtectVirtualMemory/LdrInitializeThunk failed: %#x", rcNt);
3027
3028 SIZE_T cbIgnored;
3029 rcNt = NtWriteVirtualMemory(hProcess, pvLdrInitThunk, pabBackup, cbBackup, &cbIgnored);
3030 if (!NT_SUCCESS(rcNt))
3031 return RTErrInfoSetF(pErrInfo, VERR_GENERAL_FAILURE,
3032 "supR3HardNtEnableThreadCreation: NtWriteVirtualMemory/LdrInitializeThunk[restore] failed: %#x",
3033 rcNt);
3034
3035 pvProt = pvLdrInitThunk;
3036 cbProt = cbBackup;
3037 rcNt = NtProtectVirtualMemory(hProcess, &pvProt, &cbProt, fOldProt, &fOldProt);
3038 if (!NT_SUCCESS(rcNt))
3039 return RTErrInfoSetF(pErrInfo, VERR_GENERAL_FAILURE,
3040 "supR3HardNtEnableThreadCreation: NtProtectVirtualMemory/LdrInitializeThunk[restore] failed: %#x",
3041 rcNt);
3042
3043 return VINF_SUCCESS;
3044}
3045
3046
3047/**
3048 * Disable thread creation for the current process.
3049 *
3050 * @remarks Doesn't really disables it, just makes the threads exit immediately
3051 * without executing any real code.
3052 */
3053static void supR3HardenedWinDisableThreadCreation(void)
3054{
3055 /* Cannot use the imported NtTerminateThread as it's pointing to our own
3056 syscall assembly code. */
3057 static PFNRT s_pfnNtTerminateThread = NULL;
3058 if (s_pfnNtTerminateThread == NULL)
3059 s_pfnNtTerminateThread = supR3HardenedWinGetRealDllSymbol("ntdll.dll", "NtTerminateThread");
3060 SUPR3HARDENED_ASSERT(s_pfnNtTerminateThread);
3061
3062 int rc = supR3HardNtDisableThreadCreationEx(NtCurrentProcess(),
3063 (void *)(uintptr_t)&LdrInitializeThunk,
3064 (void *)(uintptr_t)s_pfnNtTerminateThread,
3065 g_abLdrInitThunkSelfBackup, sizeof(g_abLdrInitThunkSelfBackup),
3066 NULL /* pErrInfo*/);
3067 g_fSupInitThunkSelfPatched = RT_SUCCESS(rc);
3068}
3069
3070
3071/**
3072 * Undoes the effects of supR3HardenedWinDisableThreadCreation.
3073 */
3074DECLHIDDEN(void) supR3HardenedWinEnableThreadCreation(void)
3075{
3076 if (g_fSupInitThunkSelfPatched)
3077 {
3078 int rc = supR3HardNtEnableThreadCreationEx(NtCurrentProcess(),
3079 (void *)(uintptr_t)&LdrInitializeThunk,
3080 g_abLdrInitThunkSelfBackup, sizeof(g_abLdrInitThunkSelfBackup),
3081 RTErrInfoInitStatic(&g_ErrInfoStatic));
3082 if (RT_FAILURE(rc))
3083 supR3HardenedError(rc, true /*fFatal*/, "%s", g_ErrInfoStatic.szMsg);
3084 g_fSupInitThunkSelfPatched = false;
3085 }
3086}
3087
3088
3089
3090
3091/*
3092 *
3093 * R e s p a w n
3094 * R e s p a w n
3095 * R e s p a w n
3096 *
3097 */
3098
3099
3100/**
[51770]3101 * Gets the SID of the user associated with the process.
3102 *
3103 * @returns @c true if we've got a login SID, @c false if not.
3104 * @param pSidUser Where to return the user SID.
3105 * @param cbSidUser The size of the user SID buffer.
3106 * @param pSidLogin Where to return the login SID.
3107 * @param cbSidLogin The size of the login SID buffer.
3108 */
[52969]3109static bool supR3HardNtChildGetUserAndLogSids(PSID pSidUser, ULONG cbSidUser, PSID pSidLogin, ULONG cbSidLogin)
[51770]3110{
3111 HANDLE hToken;
[52163]3112 SUPR3HARDENED_ASSERT_NT_SUCCESS(NtOpenProcessToken(NtCurrentProcess(), TOKEN_QUERY, &hToken));
[51770]3113 union
3114 {
3115 TOKEN_USER UserInfo;
3116 TOKEN_GROUPS Groups;
3117 uint8_t abPadding[4096];
3118 } uBuf;
3119 ULONG cbRet = 0;
3120 SUPR3HARDENED_ASSERT_NT_SUCCESS(NtQueryInformationToken(hToken, TokenUser, &uBuf, sizeof(uBuf), &cbRet));
3121 SUPR3HARDENED_ASSERT_NT_SUCCESS(RtlCopySid(cbSidUser, pSidUser, uBuf.UserInfo.User.Sid));
3122
3123 bool fLoginSid = false;
3124 NTSTATUS rcNt = NtQueryInformationToken(hToken, TokenLogonSid, &uBuf, sizeof(uBuf), &cbRet);
3125 if (NT_SUCCESS(rcNt))
3126 {
3127 for (DWORD i = 0; i < uBuf.Groups.GroupCount; i++)
3128 if ((uBuf.Groups.Groups[i].Attributes & SE_GROUP_LOGON_ID) == SE_GROUP_LOGON_ID)
3129 {
3130 SUPR3HARDENED_ASSERT_NT_SUCCESS(RtlCopySid(cbSidLogin, pSidLogin, uBuf.Groups.Groups[i].Sid));
3131 fLoginSid = true;
3132 break;
3133 }
3134 }
3135
3136 SUPR3HARDENED_ASSERT_NT_SUCCESS(NtClose(hToken));
3137
3138 return fLoginSid;
3139}
3140
3141
3142/**
3143 * Build security attributes for the process or the primary thread (@a fProcess)
3144 *
3145 * Process DACLs can be bypassed using the SeDebugPrivilege (generally available
3146 * to admins, i.e. normal windows users), or by taking ownership and/or
3147 * modifying the DACL. However, it restricts
3148 *
3149 * @param pSecAttrs Where to return the security attributes.
3150 * @param pCleanup Cleanup record.
3151 * @param fProcess Set if it's for the process, clear if it's for
3152 * the primary thread.
3153 */
[52969]3154static void supR3HardNtChildInitSecAttrs(PSECURITY_ATTRIBUTES pSecAttrs, PMYSECURITYCLEANUP pCleanup, bool fProcess)
[51770]3155{
3156 /*
3157 * Safe return values.
3158 */
3159 suplibHardenedMemSet(pCleanup, 0, sizeof(*pCleanup));
3160
3161 pSecAttrs->nLength = sizeof(*pSecAttrs);
3162 pSecAttrs->bInheritHandle = FALSE;
3163 pSecAttrs->lpSecurityDescriptor = NULL;
3164
3165/** @todo This isn't at all complete, just sketches... */
3166
3167 /*
3168 * Create an ACL detailing the access of the above groups.
3169 */
3170 SUPR3HARDENED_ASSERT_NT_SUCCESS(RtlCreateAcl(&pCleanup->Acl.AclHdr, sizeof(pCleanup->Acl), ACL_REVISION));
3171
[52633]3172 ULONG fDeny = DELETE | WRITE_DAC | WRITE_OWNER;
[51770]3173 ULONG fAllow = SYNCHRONIZE | READ_CONTROL;
3174 ULONG fAllowLogin = SYNCHRONIZE | READ_CONTROL;
3175 if (fProcess)
3176 {
3177 fDeny |= PROCESS_CREATE_THREAD | PROCESS_SET_SESSIONID | PROCESS_VM_OPERATION | PROCESS_VM_WRITE
3178 | PROCESS_CREATE_PROCESS | PROCESS_DUP_HANDLE | PROCESS_SET_QUOTA
3179 | PROCESS_SET_INFORMATION | PROCESS_SUSPEND_RESUME;
3180 fAllow |= PROCESS_TERMINATE | PROCESS_VM_READ | PROCESS_QUERY_INFORMATION;
3181 fAllowLogin |= PROCESS_TERMINATE | PROCESS_VM_READ | PROCESS_QUERY_INFORMATION;
3182 if (g_uNtVerCombined >= SUP_MAKE_NT_VER_SIMPLE(6, 0)) /* Introduced in Vista. */
3183 {
3184 fAllow |= PROCESS_QUERY_LIMITED_INFORMATION;
3185 fAllowLogin |= PROCESS_QUERY_LIMITED_INFORMATION;
3186 }
3187 if (g_uNtVerCombined >= SUP_MAKE_NT_VER_SIMPLE(6, 3)) /* Introduced in Windows 8.1. */
3188 fAllow |= PROCESS_SET_LIMITED_INFORMATION;
3189 }
3190 else
3191 {
3192 fDeny |= THREAD_SUSPEND_RESUME | THREAD_SET_CONTEXT | THREAD_SET_INFORMATION | THREAD_SET_THREAD_TOKEN
3193 | THREAD_IMPERSONATE | THREAD_DIRECT_IMPERSONATION;
3194 fAllow |= THREAD_GET_CONTEXT | THREAD_QUERY_INFORMATION;
3195 fAllowLogin |= THREAD_GET_CONTEXT | THREAD_QUERY_INFORMATION;
3196 if (g_uNtVerCombined >= SUP_MAKE_NT_VER_SIMPLE(6, 0)) /* Introduced in Vista. */
3197 {
3198 fAllow |= THREAD_QUERY_LIMITED_INFORMATION | THREAD_SET_LIMITED_INFORMATION;
3199 fAllowLogin |= THREAD_QUERY_LIMITED_INFORMATION;
3200 }
3201
3202 }
3203 fDeny |= ~fAllow & (SPECIFIC_RIGHTS_ALL | STANDARD_RIGHTS_ALL);
3204
3205 /* Deny everyone access to bad bits. */
3206#if 1
3207 SID_IDENTIFIER_AUTHORITY SIDAuthWorld = SECURITY_WORLD_SID_AUTHORITY;
3208 SUPR3HARDENED_ASSERT_NT_SUCCESS(RtlInitializeSid(&pCleanup->Everyone.Sid, &SIDAuthWorld, 1));
3209 *RtlSubAuthoritySid(