VirtualBox

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

Last change on this file was 104384, checked in by vboxsync, 2 months ago

/Config.kmk,SUPHardNt: s/VBOX_WITHOUT_HARDENING_INTEGRITY_CHECK/VBOX_WITHOUT_WINDOWS_KERNEL_CODE_SIGNING_CERT/; s/SUPHNTVI_F_TRUSTED_INSTALLER_OWNER/SUPHNTVI_F_TRUSTED_INSTALLER_OR_SIMILAR_OWNER/; some more comments, a @todo and dialing back the changes a little. bugref:10657

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 306.2 KB
Line 
1/* $Id: SUPR3HardenedMain-win.cpp 104384 2024-04-19 22:03:10Z vboxsync $ */
2/** @file
3 * VirtualBox Support Library - Hardened main(), windows bits.
4 */
5
6/*
7 * Copyright (C) 2006-2023 Oracle and/or its affiliates.
8 *
9 * This file is part of VirtualBox base platform packages, as
10 * available from https://www.virtualbox.org.
11 *
12 * This program is free software; you can redistribute it and/or
13 * modify it under the terms of the GNU General Public License
14 * as published by the Free Software Foundation, in version 3 of the
15 * License.
16 *
17 * This program is distributed in the hope that it will be useful, but
18 * WITHOUT ANY WARRANTY; without even the implied warranty of
19 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
20 * General Public License for more details.
21 *
22 * You should have received a copy of the GNU General Public License
23 * along with this program; if not, see <https://www.gnu.org/licenses>.
24 *
25 * The contents of this file may alternatively be used under the terms
26 * of the Common Development and Distribution License Version 1.0
27 * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
28 * in the VirtualBox distribution, in which case the provisions of the
29 * CDDL are applicable instead of those of the GPL.
30 *
31 * You may elect to license modified versions of this file under the
32 * terms and conditions of either the GPL or the CDDL or both.
33 *
34 * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
35 */
36
37
38/*********************************************************************************************************************************
39* Header Files *
40*********************************************************************************************************************************/
41#include <iprt/nt/nt-and-windows.h>
42#include <AccCtrl.h>
43#include <AclApi.h>
44#ifndef PROCESS_SET_LIMITED_INFORMATION
45# define PROCESS_SET_LIMITED_INFORMATION 0x2000
46#endif
47#ifndef LOAD_LIBRARY_SEARCH_APPLICATION_DIR
48# define LOAD_LIBRARY_SEARCH_DLL_LOAD_DIR UINT32_C(0x100)
49# define LOAD_LIBRARY_SEARCH_APPLICATION_DIR UINT32_C(0x200)
50# define LOAD_LIBRARY_SEARCH_USER_DIRS UINT32_C(0x400)
51# define LOAD_LIBRARY_SEARCH_SYSTEM32 UINT32_C(0x800)
52#endif
53
54#include <VBox/sup.h>
55#include <VBox/err.h>
56#include <VBox/dis.h>
57#include <iprt/ctype.h>
58#include <iprt/string.h>
59#include <iprt/initterm.h>
60#include <iprt/param.h>
61#include <iprt/path.h>
62#include <iprt/thread.h>
63#include <iprt/utf16.h>
64#include <iprt/zero.h>
65
66#include "SUPLibInternal.h"
67#include "win/SUPHardenedVerify-win.h"
68#include "../SUPDrvIOC.h"
69
70#ifndef IMAGE_SCN_TYPE_NOLOAD
71# define IMAGE_SCN_TYPE_NOLOAD 0x00000002
72#endif
73
74
75/*********************************************************************************************************************************
76* Defined Constants And Macros *
77*********************************************************************************************************************************/
78/** The first argument of a respawed stub when respawned for the first time.
79 * This just needs to be unique enough to avoid most confusion with real
80 * executable names, there are other checks in place to make sure we've respanwed. */
81#define SUPR3_RESPAWN_1_ARG0 "60eaff78-4bdd-042d-2e72-669728efd737-suplib-2ndchild"
82
83/** The first argument of a respawed stub when respawned for the second time.
84 * This just needs to be unique enough to avoid most confusion with real
85 * executable names, there are other checks in place to make sure we've respanwed. */
86#define SUPR3_RESPAWN_2_ARG0 "60eaff78-4bdd-042d-2e72-669728efd737-suplib-3rdchild"
87
88/** Unconditional assertion. */
89#define SUPR3HARDENED_ASSERT(a_Expr) \
90 do { \
91 if (!(a_Expr)) \
92 supR3HardenedFatal("%s: %s\n", __FUNCTION__, #a_Expr); \
93 } while (0)
94
95/** Unconditional assertion of NT_SUCCESS. */
96#define SUPR3HARDENED_ASSERT_NT_SUCCESS(a_Expr) \
97 do { \
98 NTSTATUS rcNtAssert = (a_Expr); \
99 if (!NT_SUCCESS(rcNtAssert)) \
100 supR3HardenedFatal("%s: %s -> %#x\n", __FUNCTION__, #a_Expr, rcNtAssert); \
101 } while (0)
102
103/** Unconditional assertion of a WIN32 API returning non-FALSE. */
104#define SUPR3HARDENED_ASSERT_WIN32_SUCCESS(a_Expr) \
105 do { \
106 BOOL fRcAssert = (a_Expr); \
107 if (fRcAssert == FALSE) \
108 supR3HardenedFatal("%s: %s -> %#x\n", __FUNCTION__, #a_Expr, RtlGetLastWin32Error()); \
109 } while (0)
110
111
112/*********************************************************************************************************************************
113* Structures and Typedefs *
114*********************************************************************************************************************************/
115/**
116 * Security descriptor cleanup structure.
117 */
118typedef struct MYSECURITYCLEANUP
119{
120 union
121 {
122 SID Sid;
123 uint8_t abPadding[SECURITY_MAX_SID_SIZE];
124 } Everyone, Owner, User, Login;
125 union
126 {
127 ACL AclHdr;
128 uint8_t abPadding[1024];
129 } Acl;
130 PSECURITY_DESCRIPTOR pSecDesc;
131} MYSECURITYCLEANUP;
132/** Pointer to security cleanup structure. */
133typedef MYSECURITYCLEANUP *PMYSECURITYCLEANUP;
134
135
136/**
137 * Image verifier cache entry.
138 */
139typedef struct VERIFIERCACHEENTRY
140{
141 /** Pointer to the next entry with the same hash value. */
142 struct VERIFIERCACHEENTRY * volatile pNext;
143 /** Next entry in the WinVerifyTrust todo list. */
144 struct VERIFIERCACHEENTRY * volatile pNextTodoWvt;
145
146 /** The file handle. */
147 HANDLE hFile;
148 /** If fIndexNumber is set, this is an file system internal file identifier. */
149 LARGE_INTEGER IndexNumber;
150 /** The path hash value. */
151 uint32_t uHash;
152 /** The verification result. */
153 int rc;
154 /** Used for shutting up load and error messages after a while so they don't
155 * flood the log file and fill up the disk. */
156 uint32_t volatile cHits;
157 /** The validation flags (for WinVerifyTrust retry). */
158 uint32_t fFlags;
159 /** Whether IndexNumber is valid */
160 bool fIndexNumberValid;
161 /** Whether verified by WinVerifyTrust. */
162 bool volatile fWinVerifyTrust;
163 /** cwcPath * sizeof(RTUTF16). */
164 uint16_t cbPath;
165 /** The full path of this entry (variable size). */
166 RTUTF16 wszPath[1];
167} VERIFIERCACHEENTRY;
168/** Pointer to an image verifier path entry. */
169typedef VERIFIERCACHEENTRY *PVERIFIERCACHEENTRY;
170
171
172/**
173 * Name of an import DLL that we need to check out.
174 */
175typedef struct VERIFIERCACHEIMPORT
176{
177 /** Pointer to the next DLL in the list. */
178 struct VERIFIERCACHEIMPORT * volatile pNext;
179 /** The length of pwszAltSearchDir if available. */
180 uint32_t cwcAltSearchDir;
181 /** This points the directory containing the DLL needing it, this will be
182 * NULL for a System32 DLL. */
183 PWCHAR pwszAltSearchDir;
184 /** The name of the import DLL (variable length). */
185 char szName[1];
186} VERIFIERCACHEIMPORT;
187/** Pointer to a import DLL that needs checking out. */
188typedef VERIFIERCACHEIMPORT *PVERIFIERCACHEIMPORT;
189
190
191/**
192 * Child requests.
193 */
194typedef enum SUPR3WINCHILDREQ
195{
196 /** Perform child purification and close full access handles (must be zero). */
197 kSupR3WinChildReq_PurifyChildAndCloseHandles = 0,
198 /** Close the events, we're good on our own from here on. */
199 kSupR3WinChildReq_CloseEvents,
200 /** Reporting error. */
201 kSupR3WinChildReq_Error,
202 /** End of valid requests. */
203 kSupR3WinChildReq_End
204} SUPR3WINCHILDREQ;
205
206/**
207 * Child process parameters.
208 */
209typedef struct SUPR3WINPROCPARAMS
210{
211 /** The event semaphore the child will be waiting on. */
212 HANDLE hEvtChild;
213 /** The event semaphore the parent will be waiting on. */
214 HANDLE hEvtParent;
215
216 /** The address of the NTDLL. This is only valid during the very early
217 * initialization as we abuse for thread creation protection. */
218 uintptr_t uNtDllAddr;
219
220 /** The requested operation (set by the child). */
221 SUPR3WINCHILDREQ enmRequest;
222 /** The last status. */
223 int32_t rc;
224 /** The init operation the error relates to if message, kSupInitOp_Invalid if
225 * not message. */
226 SUPINITOP enmWhat;
227 /** Where if message. */
228 char szWhere[80];
229 /** Error message / path name string space. */
230 char szErrorMsg[16384+1024];
231} SUPR3WINPROCPARAMS;
232
233
234/**
235 * Child process data structure for use during child process init setup and
236 * purification.
237 */
238typedef struct SUPR3HARDNTCHILD
239{
240 /** Process handle. */
241 HANDLE hProcess;
242 /** Primary thread handle. */
243 HANDLE hThread;
244 /** Handle to the parent process, if we're the middle (stub) process. */
245 HANDLE hParent;
246 /** The event semaphore the child will be waiting on. */
247 HANDLE hEvtChild;
248 /** The event semaphore the parent will be waiting on. */
249 HANDLE hEvtParent;
250 /** The address of NTDLL in the child. */
251 uintptr_t uNtDllAddr;
252 /** The address of NTDLL in this process. */
253 uintptr_t uNtDllParentAddr;
254 /** Which respawn number this is (1 = stub, 2 = VM). */
255 int iWhich;
256 /** The basic process info. */
257 PROCESS_BASIC_INFORMATION BasicInfo;
258 /** The probable size of the PEB. */
259 size_t cbPeb;
260 /** The pristine process environment block. */
261 PEB Peb;
262 /** The child process parameters. */
263 SUPR3WINPROCPARAMS ProcParams;
264} SUPR3HARDNTCHILD;
265/** Pointer to a child process data structure. */
266typedef SUPR3HARDNTCHILD *PSUPR3HARDNTCHILD;
267
268
269/*********************************************************************************************************************************
270* Global Variables *
271*********************************************************************************************************************************/
272/** Process parameters. Specified by parent if VM process, see
273 * supR3HardenedVmProcessInit. */
274static SUPR3WINPROCPARAMS g_ProcParams = { NULL, NULL, 0, (SUPR3WINCHILDREQ)0, 0 };
275/** Set if supR3HardenedEarlyProcessInit was invoked. */
276bool g_fSupEarlyProcessInit = false;
277/** Set if the stub device has been opened (stub process only). */
278bool g_fSupStubOpened = false;
279
280/** @name Global variables initialized by suplibHardenedWindowsMain.
281 * @{ */
282/** Combined windows NT version number. See SUP_MAKE_NT_VER_COMBINED. */
283uint32_t g_uNtVerCombined = 0;
284/** Count calls to the special main function for linking santity checks. */
285static uint32_t volatile g_cSuplibHardenedWindowsMainCalls;
286/** The UTF-16 windows path to the executable. */
287RTUTF16 g_wszSupLibHardenedExePath[1024];
288/** The NT path of the executable. */
289SUPSYSROOTDIRBUF g_SupLibHardenedExeNtPath;
290/** The NT path of the application binary directory. */
291SUPSYSROOTDIRBUF g_SupLibHardenedAppBinNtPath;
292/** The offset into g_SupLibHardenedExeNtPath of the executable name (WCHAR,
293 * not byte). This also gives the length of the exectuable directory path,
294 * including a trailing slash. */
295static uint32_t g_offSupLibHardenedExeNtName;
296/** Set if we need to use the LOAD_LIBRARY_SEARCH_USER_DIRS option. */
297bool g_fSupLibHardenedDllSearchUserDirs = false;
298/** @} */
299
300/** @name Hook related variables.
301 * @{ */
302/** Pointer to the bit of assembly code that will perform the original
303 * NtCreateSection operation. */
304static NTSTATUS (NTAPI *g_pfnNtCreateSectionReal)(PHANDLE, ACCESS_MASK, POBJECT_ATTRIBUTES,
305 PLARGE_INTEGER, ULONG, ULONG, HANDLE);
306/** Pointer to the NtCreateSection function in NtDll (for patching purposes). */
307static uint8_t *g_pbNtCreateSection;
308/** The patched NtCreateSection bytes (for restoring). */
309static uint8_t g_abNtCreateSectionPatch[16];
310/** Pointer to the bit of assembly code that will perform the original
311 * LdrLoadDll operation. */
312static NTSTATUS (NTAPI *g_pfnLdrLoadDllReal)(PWSTR, PULONG, PUNICODE_STRING, PHANDLE);
313/** Pointer to the LdrLoadDll function in NtDll (for patching purposes). */
314static uint8_t *g_pbLdrLoadDll;
315/** The patched LdrLoadDll bytes (for restoring). */
316static uint8_t g_abLdrLoadDllPatch[16];
317
318#ifndef VBOX_WITHOUT_HARDENDED_XCPT_LOGGING
319/** Pointer to the bit of assembly code that will perform the original
320 * KiUserExceptionDispatcher operation. */
321static VOID (NTAPI *g_pfnKiUserExceptionDispatcherReal)(void);
322/** Pointer to the KiUserExceptionDispatcher function in NtDll (for patching purposes). */
323static uint8_t *g_pbKiUserExceptionDispatcher;
324/** The patched KiUserExceptionDispatcher bytes (for restoring). */
325static uint8_t g_abKiUserExceptionDispatcherPatch[16];
326#endif
327
328/** Pointer to the bit of assembly code that will perform the original
329 * KiUserApcDispatcher operation. */
330static VOID (NTAPI *g_pfnKiUserApcDispatcherReal)(void);
331/** Pointer to the KiUserApcDispatcher function in NtDll (for patching purposes). */
332static uint8_t *g_pbKiUserApcDispatcher;
333/** The patched KiUserApcDispatcher bytes (for restoring). */
334static uint8_t g_abKiUserApcDispatcherPatch[16];
335
336/** Pointer to the LdrInitializeThunk function in NtDll for
337 * supR3HardenedMonitor_KiUserApcDispatcher_C() to use for APC vetting. */
338static uintptr_t g_pfnLdrInitializeThunk;
339
340/** The hash table of verifier cache . */
341static PVERIFIERCACHEENTRY volatile g_apVerifierCache[128];
342/** Queue of cached images which needs WinVerifyTrust to check them. */
343static PVERIFIERCACHEENTRY volatile g_pVerifierCacheTodoWvt = NULL;
344/** Queue of cached images which needs their imports checked. */
345static PVERIFIERCACHEIMPORT volatile g_pVerifierCacheTodoImports = NULL;
346
347/** The windows path to dir \\SystemRoot\\System32 directory (technically
348 * this whatever \\KnownDlls\\KnownDllPath points to). */
349SUPSYSROOTDIRBUF g_System32WinPath;
350/** @} */
351
352/** Positive if the DLL notification callback has been registered, counts
353 * registration attempts as negative. */
354static int g_cDllNotificationRegistered = 0;
355/** The registration cookie of the DLL notification callback. */
356static PVOID g_pvDllNotificationCookie = NULL;
357
358/** Static error info structure used during init. */
359static RTERRINFOSTATIC g_ErrInfoStatic;
360
361/** In the assembly file. */
362extern "C" uint8_t g_abSupHardReadWriteExecPage[PAGE_SIZE];
363
364/** Whether we've patched our own LdrInitializeThunk or not. We do this to
365 * disable thread creation. */
366static bool g_fSupInitThunkSelfPatched;
367/** The backup of our own LdrInitializeThunk code, for enabling and disabling
368 * thread creation in this process. */
369static uint8_t g_abLdrInitThunkSelfBackup[16];
370
371/** Mask of adversaries that we've detected (SUPHARDNT_ADVERSARY_XXX). */
372static uint32_t g_fSupAdversaries = 0;
373/** @name SUPHARDNT_ADVERSARY_XXX - Adversaries
374 * @{ */
375/** Symantec endpoint protection or similar including SysPlant.sys. */
376#define SUPHARDNT_ADVERSARY_SYMANTEC_SYSPLANT RT_BIT_32(0)
377/** Symantec Norton 360. */
378#define SUPHARDNT_ADVERSARY_SYMANTEC_N360 RT_BIT_32(1)
379/** Avast! */
380#define SUPHARDNT_ADVERSARY_AVAST RT_BIT_32(2)
381/** TrendMicro OfficeScan and probably others. */
382#define SUPHARDNT_ADVERSARY_TRENDMICRO RT_BIT_32(3)
383/** TrendMicro potentially buggy sakfile.sys. */
384#define SUPHARDNT_ADVERSARY_TRENDMICRO_SAKFILE RT_BIT_32(4)
385/** McAfee. */
386#define SUPHARDNT_ADVERSARY_MCAFEE RT_BIT_32(5)
387/** Kaspersky or OEMs of it. */
388#define SUPHARDNT_ADVERSARY_KASPERSKY RT_BIT_32(6)
389/** Malwarebytes Anti-Malware (MBAM). */
390#define SUPHARDNT_ADVERSARY_MBAM RT_BIT_32(7)
391/** AVG Internet Security. */
392#define SUPHARDNT_ADVERSARY_AVG RT_BIT_32(8)
393/** Panda Security. */
394#define SUPHARDNT_ADVERSARY_PANDA RT_BIT_32(9)
395/** Microsoft Security Essentials. */
396#define SUPHARDNT_ADVERSARY_MSE RT_BIT_32(10)
397/** Comodo. */
398#define SUPHARDNT_ADVERSARY_COMODO RT_BIT_32(11)
399/** Check Point's Zone Alarm (may include Kaspersky). */
400#define SUPHARDNT_ADVERSARY_ZONE_ALARM RT_BIT_32(12)
401/** Digital guardian, old problematic version. */
402#define SUPHARDNT_ADVERSARY_DIGITAL_GUARDIAN_OLD RT_BIT_32(13)
403/** Digital guardian, new version. */
404#define SUPHARDNT_ADVERSARY_DIGITAL_GUARDIAN_NEW RT_BIT_32(14)
405/** Cylance protect or something (from googling, no available sample copy). */
406#define SUPHARDNT_ADVERSARY_CYLANCE RT_BIT_32(15)
407/** BeyondTrust / PowerBroker / something (googling, no available sample copy). */
408#define SUPHARDNT_ADVERSARY_BEYONDTRUST RT_BIT_32(16)
409/** Avecto / Defendpoint / Privilege Guard (details from support guy, hoping to get sample copy). */
410#define SUPHARDNT_ADVERSARY_AVECTO RT_BIT_32(17)
411/** Sophos Endpoint Defense. */
412#define SUPHARDNT_ADVERSARY_SOPHOS RT_BIT_32(18)
413/** VMware horizon view agent. */
414#define SUPHARDNT_ADVERSARY_HORIZON_VIEW_AGENT RT_BIT_32(19)
415/** Unknown adversary detected while waiting on child. */
416#define SUPHARDNT_ADVERSARY_UNKNOWN RT_BIT_32(31)
417/** @} */
418
419
420/*********************************************************************************************************************************
421* Internal Functions *
422*********************************************************************************************************************************/
423static NTSTATUS supR3HardenedScreenImage(HANDLE hFile, bool fImage, bool fIgnoreArch, PULONG pfAccess, PULONG pfProtect,
424 bool *pfCallRealApi, const char *pszCaller, bool fAvoidWinVerifyTrust,
425 bool *pfQuiet) RT_NOTHROW_PROTO;
426static void supR3HardenedWinRegisterDllNotificationCallback(void);
427static void supR3HardenedWinReInstallHooks(bool fFirst) RT_NOTHROW_PROTO;
428DECLASM(void) supR3HardenedEarlyProcessInitThunk(void);
429DECLASM(void) supR3HardenedMonitor_KiUserApcDispatcher(void);
430#ifndef VBOX_WITHOUT_HARDENDED_XCPT_LOGGING
431DECLASM(void) supR3HardenedMonitor_KiUserExceptionDispatcher(void);
432#endif
433extern "C" void __stdcall suplibHardenedWindowsMain(void);
434
435
436#if 0 /* unused */
437
438/**
439 * Simple wide char search routine.
440 *
441 * @returns Pointer to the first location of @a wcNeedle in @a pwszHaystack.
442 * NULL if not found.
443 * @param pwszHaystack Pointer to the string that should be searched.
444 * @param wcNeedle The character to search for.
445 */
446static PRTUTF16 suplibHardenedWStrChr(PCRTUTF16 pwszHaystack, RTUTF16 wcNeedle)
447{
448 for (;;)
449 {
450 RTUTF16 wcCur = *pwszHaystack;
451 if (wcCur == wcNeedle)
452 return (PRTUTF16)pwszHaystack;
453 if (wcCur == '\0')
454 return NULL;
455 pwszHaystack++;
456 }
457}
458
459
460/**
461 * Simple wide char string length routine.
462 *
463 * @returns The number of characters in the given string. (Excludes the
464 * terminator.)
465 * @param pwsz The string.
466 */
467static size_t suplibHardenedWStrLen(PCRTUTF16 pwsz)
468{
469 PCRTUTF16 pwszCur = pwsz;
470 while (*pwszCur != '\0')
471 pwszCur++;
472 return pwszCur - pwsz;
473}
474
475#endif /* unused */
476
477
478/**
479 * Our version of GetTickCount.
480 * @returns Millisecond timestamp.
481 */
482static uint64_t supR3HardenedWinGetMilliTS(void)
483{
484 PKUSER_SHARED_DATA pUserSharedData = (PKUSER_SHARED_DATA)(uintptr_t)0x7ffe0000;
485
486 /* use interrupt time */
487 LARGE_INTEGER Time;
488 do
489 {
490 Time.HighPart = pUserSharedData->InterruptTime.High1Time;
491 Time.LowPart = pUserSharedData->InterruptTime.LowPart;
492 } while (pUserSharedData->InterruptTime.High2Time != Time.HighPart);
493
494 return (uint64_t)Time.QuadPart / 10000;
495}
496
497
498/**
499 * Called when there is some /GS (or maybe /RTCsu) related stack problem.
500 *
501 * We don't want the CRT version living in gshandle.obj, as it uses a lot of
502 * kernel32 imports, we want to report this error ourselves.
503 */
504extern "C" __declspec(noreturn guard(nosspro) guard(nossepi))
505void __cdecl __report_rangecheckfailure(void)
506{
507 supR3HardenedFatal("__report_rangecheckfailure called from %p", ASMReturnAddress());
508}
509
510
511/**
512 * Called when there is some /GS problem has been detected.
513 *
514 * We don't want the CRT version living in gshandle.obj, as it uses a lot of
515 * kernel32 imports, we want to report this error ourselves.
516 */
517extern "C" __declspec(noreturn guard(nosspro) guard(nossepi))
518#ifdef RT_ARCH_X86
519void __cdecl __report_gsfailure(void)
520#else
521void __report_gsfailure(uintptr_t uCookie)
522#endif
523{
524#ifdef RT_ARCH_X86
525 supR3HardenedFatal("__report_gsfailure called from %p", ASMReturnAddress());
526#else
527 supR3HardenedFatal("__report_gsfailure called from %p, cookie=%p", ASMReturnAddress(), uCookie);
528#endif
529}
530
531
532/**
533 * Wrapper around LoadLibraryEx that deals with the UTF-8 to UTF-16 conversion
534 * and supplies the right flags.
535 *
536 * @returns Module handle on success, NULL on failure.
537 * @param pszName The full path to the DLL.
538 * @param fSystem32Only Whether to only look for imports in the system32
539 * directory. If set to false, the application
540 * directory is also searched.
541 * @param fMainFlags The main flags (giving the location), if the DLL
542 * being loaded is loaded from the app bin
543 * directory and import other DLLs from there. Pass
544 * 0 (= SUPSECMAIN_FLAGS_LOC_APP_BIN) if not
545 * applicable. Ignored if @a fSystem32Only is set.
546 *
547 * This is only needed to load VBoxRT.dll when
548 * executing a testcase from the testcase/ subdir.
549 */
550DECLHIDDEN(void *) supR3HardenedWinLoadLibrary(const char *pszName, bool fSystem32Only, uint32_t fMainFlags)
551{
552 WCHAR wszPath[RTPATH_MAX];
553 PRTUTF16 pwszPath = wszPath;
554 int rc = RTStrToUtf16Ex(pszName, RTSTR_MAX, &pwszPath, RT_ELEMENTS(wszPath), NULL);
555 if (RT_SUCCESS(rc))
556 {
557 while (*pwszPath)
558 {
559 if (*pwszPath == '/')
560 *pwszPath = '\\';
561 pwszPath++;
562 }
563
564 DWORD fFlags = 0;
565 if (g_uNtVerCombined >= SUP_MAKE_NT_VER_SIMPLE(6, 0))
566 {
567 fFlags |= LOAD_LIBRARY_SEARCH_SYSTEM32;
568 if (!fSystem32Only)
569 {
570 fFlags |= LOAD_LIBRARY_SEARCH_APPLICATION_DIR;
571 if (g_fSupLibHardenedDllSearchUserDirs)
572 fFlags |= LOAD_LIBRARY_SEARCH_USER_DIRS;
573 if ((fMainFlags & SUPSECMAIN_FLAGS_LOC_MASK) != SUPSECMAIN_FLAGS_LOC_APP_BIN)
574 fFlags |= LOAD_LIBRARY_SEARCH_DLL_LOAD_DIR;
575 }
576 }
577
578 void *pvRet = (void *)LoadLibraryExW(wszPath, NULL /*hFile*/, fFlags);
579
580 /* Vista, W7, W2K8R might not work without KB2533623, so retry with no flags. */
581 if ( !pvRet
582 && fFlags
583 && g_uNtVerCombined < SUP_MAKE_NT_VER_SIMPLE(6, 2)
584 && RtlGetLastWin32Error() == ERROR_INVALID_PARAMETER)
585 pvRet = (void *)LoadLibraryExW(wszPath, NULL /*hFile*/, 0);
586
587 return pvRet;
588 }
589 supR3HardenedFatal("RTStrToUtf16Ex failed on '%s': %Rrc", pszName, rc);
590 /* not reached */
591}
592
593
594/**
595 * Gets the internal index number of the file.
596 *
597 * @returns True if we got an index number, false if not.
598 * @param hFile The file in question.
599 * @param pIndexNumber where to return the index number.
600 */
601static bool supR3HardenedWinVerifyCacheGetIndexNumber(HANDLE hFile, PLARGE_INTEGER pIndexNumber) RT_NOTHROW_DEF
602{
603 IO_STATUS_BLOCK Ios = RTNT_IO_STATUS_BLOCK_INITIALIZER;
604 NTSTATUS rcNt = NtQueryInformationFile(hFile, &Ios, pIndexNumber, sizeof(*pIndexNumber), FileInternalInformation);
605 if (NT_SUCCESS(rcNt))
606 rcNt = Ios.Status;
607#ifdef DEBUG_bird
608 if (!NT_SUCCESS(rcNt))
609 __debugbreak();
610#endif
611 return NT_SUCCESS(rcNt) && pIndexNumber->QuadPart != 0;
612}
613
614
615/**
616 * Calculates the hash value for the given UTF-16 path string.
617 *
618 * @returns Hash value.
619 * @param pUniStr String to hash.
620 */
621static uint32_t supR3HardenedWinVerifyCacheHashPath(PCUNICODE_STRING pUniStr) RT_NOTHROW_DEF
622{
623 uint32_t uHash = 0;
624 unsigned cwcLeft = pUniStr->Length / sizeof(WCHAR);
625 PRTUTF16 pwc = pUniStr->Buffer;
626
627 while (cwcLeft-- > 0)
628 {
629 RTUTF16 wc = *pwc++;
630 if (wc < 0x80)
631 wc = wc != '/' ? RT_C_TO_LOWER(wc) : '\\';
632 uHash = wc + (uHash << 6) + (uHash << 16) - uHash;
633 }
634 return uHash;
635}
636
637
638/**
639 * Calculates the hash value for a directory + filename combo as if they were
640 * one single string.
641 *
642 * @returns Hash value.
643 * @param pawcDir The directory name.
644 * @param cwcDir The length of the directory name. RTSTR_MAX if
645 * not available.
646 * @param pszName The import name (UTF-8).
647 */
648static uint32_t supR3HardenedWinVerifyCacheHashDirAndFile(PCRTUTF16 pawcDir, uint32_t cwcDir, const char *pszName) RT_NOTHROW_DEF
649{
650 uint32_t uHash = 0;
651 while (cwcDir-- > 0)
652 {
653 RTUTF16 wc = *pawcDir++;
654 if (wc < 0x80)
655 wc = wc != '/' ? RT_C_TO_LOWER(wc) : '\\';
656 uHash = wc + (uHash << 6) + (uHash << 16) - uHash;
657 }
658
659 unsigned char ch = '\\';
660 uHash = ch + (uHash << 6) + (uHash << 16) - uHash;
661
662 while ((ch = *pszName++) != '\0')
663 {
664 ch = RT_C_TO_LOWER(ch);
665 uHash = ch + (uHash << 6) + (uHash << 16) - uHash;
666 }
667
668 return uHash;
669}
670
671
672/**
673 * Verify string cache compare function.
674 *
675 * @returns true if the strings match, false if not.
676 * @param pawcLeft The left hand string.
677 * @param pawcRight The right hand string.
678 * @param cwcToCompare The number of chars to compare.
679 */
680static bool supR3HardenedWinVerifyCacheIsMatch(PCRTUTF16 pawcLeft, PCRTUTF16 pawcRight, uint32_t cwcToCompare) RT_NOTHROW_DEF
681{
682 /* Try a quick memory compare first. */
683 if (memcmp(pawcLeft, pawcRight, cwcToCompare * sizeof(RTUTF16)) == 0)
684 return true;
685
686 /* Slow char by char compare. */
687 while (cwcToCompare-- > 0)
688 {
689 RTUTF16 wcLeft = *pawcLeft++;
690 RTUTF16 wcRight = *pawcRight++;
691 if (wcLeft != wcRight)
692 {
693 wcLeft = wcLeft != '/' ? RT_C_TO_LOWER(wcLeft) : '\\';
694 wcRight = wcRight != '/' ? RT_C_TO_LOWER(wcRight) : '\\';
695 if (wcLeft != wcRight)
696 return false;
697 }
698 }
699
700 return true;
701}
702
703
704
705/**
706 * Inserts the given verifier result into the cache.
707 *
708 * @param pUniStr The full path of the image.
709 * @param hFile The file handle - must either be entered into
710 * the cache or closed.
711 * @param rc The verifier result.
712 * @param fWinVerifyTrust Whether verified by WinVerifyTrust or not.
713 * @param fFlags The image verification flags.
714 */
715static void supR3HardenedWinVerifyCacheInsert(PCUNICODE_STRING pUniStr, HANDLE hFile, int rc,
716 bool fWinVerifyTrust, uint32_t fFlags) RT_NOTHROW_DEF
717{
718 /*
719 * Allocate and initalize a new entry.
720 */
721 PVERIFIERCACHEENTRY pEntry = (PVERIFIERCACHEENTRY)RTMemAllocZ(sizeof(VERIFIERCACHEENTRY) + pUniStr->Length);
722 if (pEntry)
723 {
724 pEntry->pNext = NULL;
725 pEntry->pNextTodoWvt = NULL;
726 pEntry->hFile = hFile;
727 pEntry->uHash = supR3HardenedWinVerifyCacheHashPath(pUniStr);
728 pEntry->rc = rc;
729 pEntry->fFlags = fFlags;
730 pEntry->cHits = 0;
731 pEntry->fWinVerifyTrust = fWinVerifyTrust;
732 pEntry->cbPath = pUniStr->Length;
733 memcpy(pEntry->wszPath, pUniStr->Buffer, pUniStr->Length);
734 pEntry->wszPath[pUniStr->Length / sizeof(WCHAR)] = '\0';
735 pEntry->fIndexNumberValid = supR3HardenedWinVerifyCacheGetIndexNumber(hFile, &pEntry->IndexNumber);
736
737 /*
738 * Try insert it, careful with concurrent code as well as potential duplicates.
739 */
740 uint32_t iHashTab = pEntry->uHash % RT_ELEMENTS(g_apVerifierCache);
741 VERIFIERCACHEENTRY * volatile *ppEntry = &g_apVerifierCache[iHashTab];
742 for (;;)
743 {
744 if (ASMAtomicCmpXchgPtr(ppEntry, pEntry, NULL))
745 {
746 if (!fWinVerifyTrust)
747 do
748 pEntry->pNextTodoWvt = g_pVerifierCacheTodoWvt;
749 while (!ASMAtomicCmpXchgPtr(&g_pVerifierCacheTodoWvt, pEntry, pEntry->pNextTodoWvt));
750
751 SUP_DPRINTF(("supR3HardenedWinVerifyCacheInsert: %ls\n", pUniStr->Buffer));
752 return;
753 }
754
755 PVERIFIERCACHEENTRY pOther = *ppEntry;
756 if (!pOther)
757 continue;
758 if ( pOther->uHash == pEntry->uHash
759 && pOther->cbPath == pEntry->cbPath
760 && supR3HardenedWinVerifyCacheIsMatch(pOther->wszPath, pEntry->wszPath, pEntry->cbPath / sizeof(RTUTF16)))
761 break;
762 ppEntry = &pOther->pNext;
763 }
764
765 /* Duplicate entry (may happen due to races). */
766 RTMemFree(pEntry);
767 }
768 NtClose(hFile);
769}
770
771
772/**
773 * Looks up an entry in the verifier hash table.
774 *
775 * @return Pointer to the entry on if found, NULL if not.
776 * @param pUniStr The full path of the image.
777 * @param hFile The file handle.
778 */
779static PVERIFIERCACHEENTRY supR3HardenedWinVerifyCacheLookup(PCUNICODE_STRING pUniStr, HANDLE hFile) RT_NOTHROW_DEF
780{
781 PRTUTF16 const pwszPath = pUniStr->Buffer;
782 uint16_t const cbPath = pUniStr->Length;
783 uint32_t uHash = supR3HardenedWinVerifyCacheHashPath(pUniStr);
784 uint32_t iHashTab = uHash % RT_ELEMENTS(g_apVerifierCache);
785 PVERIFIERCACHEENTRY pCur = g_apVerifierCache[iHashTab];
786 while (pCur)
787 {
788 if ( pCur->uHash == uHash
789 && pCur->cbPath == cbPath
790 && supR3HardenedWinVerifyCacheIsMatch(pCur->wszPath, pwszPath, cbPath / sizeof(RTUTF16)))
791 {
792
793 if (!pCur->fIndexNumberValid)
794 return pCur;
795 LARGE_INTEGER IndexNumber;
796 bool fIndexNumberValid = supR3HardenedWinVerifyCacheGetIndexNumber(hFile, &IndexNumber);
797 if ( fIndexNumberValid
798 && IndexNumber.QuadPart == pCur->IndexNumber.QuadPart)
799 return pCur;
800#ifdef DEBUG_bird
801 __debugbreak();
802#endif
803 }
804 pCur = pCur->pNext;
805 }
806 return NULL;
807}
808
809
810/**
811 * Looks up an import DLL in the verifier hash table.
812 *
813 * @return Pointer to the entry on if found, NULL if not.
814 * @param pawcDir The directory name.
815 * @param cwcDir The length of the directory name.
816 * @param pszName The import name (UTF-8).
817 */
818static PVERIFIERCACHEENTRY supR3HardenedWinVerifyCacheLookupImport(PCRTUTF16 pawcDir, uint32_t cwcDir, const char *pszName)
819{
820 uint32_t uHash = supR3HardenedWinVerifyCacheHashDirAndFile(pawcDir, cwcDir, pszName);
821 uint32_t iHashTab = uHash % RT_ELEMENTS(g_apVerifierCache);
822 uint32_t const cbPath = (uint32_t)((cwcDir + 1 + strlen(pszName)) * sizeof(RTUTF16));
823 PVERIFIERCACHEENTRY pCur = g_apVerifierCache[iHashTab];
824 while (pCur)
825 {
826 if ( pCur->uHash == uHash
827 && pCur->cbPath == cbPath)
828 {
829 if (supR3HardenedWinVerifyCacheIsMatch(pCur->wszPath, pawcDir, cwcDir))
830 {
831 if (pCur->wszPath[cwcDir] == '\\' || pCur->wszPath[cwcDir] == '/')
832 {
833 if (RTUtf16ICmpAscii(&pCur->wszPath[cwcDir + 1], pszName))
834 {
835 return pCur;
836 }
837 }
838 }
839 }
840
841 pCur = pCur->pNext;
842 }
843 return NULL;
844}
845
846
847/**
848 * Schedules the import DLLs for verification and entry into the cache.
849 *
850 * @param hLdrMod The loader module which imports should be
851 * scheduled for verification.
852 * @param pwszName The full NT path of the module.
853 */
854DECLHIDDEN(void) supR3HardenedWinVerifyCacheScheduleImports(RTLDRMOD hLdrMod, PCRTUTF16 pwszName)
855{
856 /*
857 * Any imports?
858 */
859 uint32_t cImports;
860 int rc = RTLdrQueryPropEx(hLdrMod, RTLDRPROP_IMPORT_COUNT, NULL /*pvBits*/, &cImports, sizeof(cImports), NULL);
861 if (RT_SUCCESS(rc))
862 {
863 if (cImports)
864 {
865 /*
866 * Figure out the DLL directory from pwszName.
867 */
868 PCRTUTF16 pawcDir = pwszName;
869 uint32_t cwcDir = 0;
870 uint32_t i = 0;
871 RTUTF16 wc;
872 while ((wc = pawcDir[i++]) != '\0')
873 if ((wc == '\\' || wc == '/' || wc == ':') && cwcDir + 2 != i)
874 cwcDir = i - 1;
875 if ( g_System32NtPath.UniStr.Length / sizeof(WCHAR) == cwcDir
876 && supR3HardenedWinVerifyCacheIsMatch(pawcDir, g_System32NtPath.UniStr.Buffer, cwcDir))
877 pawcDir = NULL;
878
879 /*
880 * Enumerate the imports.
881 */
882 for (i = 0; i < cImports; i++)
883 {
884 union
885 {
886 char szName[256];
887 uint32_t iImport;
888 } uBuf;
889 uBuf.iImport = i;
890 rc = RTLdrQueryPropEx(hLdrMod, RTLDRPROP_IMPORT_MODULE, NULL /*pvBits*/, &uBuf, sizeof(uBuf), NULL);
891 if (RT_SUCCESS(rc))
892 {
893 /*
894 * Skip kernel32, ntdll and API set stuff.
895 */
896 RTStrToLower(uBuf.szName);
897 if ( RTStrCmp(uBuf.szName, "kernel32.dll") == 0
898 || RTStrCmp(uBuf.szName, "kernelbase.dll") == 0
899 || RTStrCmp(uBuf.szName, "ntdll.dll") == 0
900 || RTStrNCmp(uBuf.szName, RT_STR_TUPLE("api-ms-win-")) == 0
901 || RTStrNCmp(uBuf.szName, RT_STR_TUPLE("ext-ms-win-")) == 0
902 )
903 {
904 continue;
905 }
906
907 /*
908 * Skip to the next one if it's already in the cache.
909 */
910 if (supR3HardenedWinVerifyCacheLookupImport(g_System32NtPath.UniStr.Buffer,
911 g_System32NtPath.UniStr.Length / sizeof(WCHAR),
912 uBuf.szName) != NULL)
913 {
914 SUP_DPRINTF(("supR3HardenedWinVerifyCacheScheduleImports: '%s' cached for system32\n", uBuf.szName));
915 continue;
916 }
917 if (supR3HardenedWinVerifyCacheLookupImport(g_SupLibHardenedAppBinNtPath.UniStr.Buffer,
918 g_SupLibHardenedAppBinNtPath.UniStr.Length / sizeof(CHAR),
919 uBuf.szName) != NULL)
920 {
921 SUP_DPRINTF(("supR3HardenedWinVerifyCacheScheduleImports: '%s' cached for appdir\n", uBuf.szName));
922 continue;
923 }
924 if (pawcDir && supR3HardenedWinVerifyCacheLookupImport(pawcDir, cwcDir, uBuf.szName) != NULL)
925 {
926 SUP_DPRINTF(("supR3HardenedWinVerifyCacheScheduleImports: '%s' cached for dll dir\n", uBuf.szName));
927 continue;
928 }
929
930 /* We could skip already scheduled modules, but that'll require serialization and extra work... */
931
932 /*
933 * Add it to the todo list.
934 */
935 SUP_DPRINTF(("supR3HardenedWinVerifyCacheScheduleImports: Import todo: #%u '%s'.\n", i, uBuf.szName));
936 uint32_t cbName = (uint32_t)strlen(uBuf.szName) + 1;
937 uint32_t cbNameAligned = RT_ALIGN_32(cbName, sizeof(RTUTF16));
938 uint32_t cbNeeded = RT_UOFFSETOF_DYN(VERIFIERCACHEIMPORT, szName[cbNameAligned])
939 + (pawcDir ? (cwcDir + 1) * sizeof(RTUTF16) : 0);
940 PVERIFIERCACHEIMPORT pImport = (PVERIFIERCACHEIMPORT)RTMemAllocZ(cbNeeded);
941 if (pImport)
942 {
943 /* Init it. */
944 memcpy(pImport->szName, uBuf.szName, cbName);
945 if (!pawcDir)
946 {
947 pImport->cwcAltSearchDir = 0;
948 pImport->pwszAltSearchDir = NULL;
949 }
950 else
951 {
952 pImport->cwcAltSearchDir = cwcDir;
953 pImport->pwszAltSearchDir = (PRTUTF16)&pImport->szName[cbNameAligned];
954 memcpy(pImport->pwszAltSearchDir, pawcDir, cwcDir * sizeof(RTUTF16));
955 pImport->pwszAltSearchDir[cwcDir] = '\0';
956 }
957
958 /* Insert it. */
959 do
960 pImport->pNext = g_pVerifierCacheTodoImports;
961 while (!ASMAtomicCmpXchgPtr(&g_pVerifierCacheTodoImports, pImport, pImport->pNext));
962 }
963 }
964 else
965 SUP_DPRINTF(("RTLDRPROP_IMPORT_MODULE failed with rc=%Rrc i=%#x on '%ls'\n", rc, i, pwszName));
966 }
967 }
968 else
969 SUP_DPRINTF(("'%ls' has no imports\n", pwszName));
970 }
971 else
972 SUP_DPRINTF(("RTLDRPROP_IMPORT_COUNT failed with rc=%Rrc on '%ls'\n", rc, pwszName));
973}
974
975
976/**
977 * Processes the list of import todos.
978 */
979static void supR3HardenedWinVerifyCacheProcessImportTodos(void)
980{
981 /*
982 * Work until we've got nothing more todo.
983 */
984 for (;;)
985 {
986 PVERIFIERCACHEIMPORT pTodo = ASMAtomicXchgPtrT(&g_pVerifierCacheTodoImports, NULL, PVERIFIERCACHEIMPORT);
987 if (!pTodo)
988 break;
989 do
990 {
991 PVERIFIERCACHEIMPORT pCur = pTodo;
992 pTodo = pTodo->pNext;
993
994 /*
995 * Not in the cached already?
996 */
997 if ( !supR3HardenedWinVerifyCacheLookupImport(g_System32NtPath.UniStr.Buffer,
998 g_System32NtPath.UniStr.Length / sizeof(WCHAR),
999 pCur->szName)
1000 && !supR3HardenedWinVerifyCacheLookupImport(g_SupLibHardenedAppBinNtPath.UniStr.Buffer,
1001 g_SupLibHardenedAppBinNtPath.UniStr.Length / sizeof(WCHAR),
1002 pCur->szName)
1003 && ( pCur->cwcAltSearchDir == 0
1004 || !supR3HardenedWinVerifyCacheLookupImport(pCur->pwszAltSearchDir, pCur->cwcAltSearchDir, pCur->szName)) )
1005 {
1006 /*
1007 * Try locate the imported DLL and open it.
1008 */
1009 SUP_DPRINTF(("supR3HardenedWinVerifyCacheProcessImportTodos: Processing '%s'...\n", pCur->szName));
1010
1011 NTSTATUS rcNt;
1012 NTSTATUS rcNtRedir = 0x22222222;
1013 HANDLE hFile = INVALID_HANDLE_VALUE;
1014 RTUTF16 wszPath[260 + 260]; /* Assumes we've limited the import name length to 256. */
1015 AssertCompile(sizeof(wszPath) > sizeof(g_System32NtPath));
1016
1017 /*
1018 * Check for DLL isolation / redirection / mapping.
1019 */
1020 size_t cwcName = 260;
1021 PRTUTF16 pwszName = &wszPath[0];
1022 int rc = RTStrToUtf16Ex(pCur->szName, RTSTR_MAX, &pwszName, cwcName, &cwcName);
1023 if (RT_SUCCESS(rc))
1024 {
1025 UNICODE_STRING UniStrName;
1026 UniStrName.Buffer = wszPath;
1027 UniStrName.Length = (USHORT)cwcName * sizeof(WCHAR);
1028 UniStrName.MaximumLength = UniStrName.Length + sizeof(WCHAR);
1029
1030 UNICODE_STRING UniStrStatic;
1031 UniStrStatic.Buffer = &wszPath[cwcName + 1];
1032 UniStrStatic.Length = 0;
1033 UniStrStatic.MaximumLength = (USHORT)(sizeof(wszPath) - cwcName * sizeof(WCHAR) - sizeof(WCHAR));
1034
1035 static UNICODE_STRING const s_DefaultSuffix = RTNT_CONSTANT_UNISTR(L".dll");
1036 UNICODE_STRING UniStrDynamic = { 0, 0, NULL };
1037 PUNICODE_STRING pUniStrResult = NULL;
1038
1039 rcNtRedir = RtlDosApplyFileIsolationRedirection_Ustr(1 /*fFlags*/,
1040 &UniStrName,
1041 (PUNICODE_STRING)&s_DefaultSuffix,
1042 &UniStrStatic,
1043 &UniStrDynamic,
1044 &pUniStrResult,
1045 NULL /*pNewFlags*/,
1046 NULL /*pcbFilename*/,
1047 NULL /*pcbNeeded*/);
1048 if (NT_SUCCESS(rcNtRedir))
1049 {
1050 IO_STATUS_BLOCK Ios = RTNT_IO_STATUS_BLOCK_INITIALIZER;
1051 OBJECT_ATTRIBUTES ObjAttr;
1052 InitializeObjectAttributes(&ObjAttr, pUniStrResult,
1053 OBJ_CASE_INSENSITIVE, NULL /*hRootDir*/, NULL /*pSecDesc*/);
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 {
1069 /* For accurate logging. */
1070 size_t cwcCopy = RT_MIN(pUniStrResult->Length / sizeof(RTUTF16), RT_ELEMENTS(wszPath) - 1);
1071 memcpy(wszPath, pUniStrResult->Buffer, cwcCopy * sizeof(RTUTF16));
1072 wszPath[cwcCopy] = '\0';
1073 }
1074 else
1075 hFile = INVALID_HANDLE_VALUE;
1076 RtlFreeUnicodeString(&UniStrDynamic);
1077 }
1078 }
1079 else
1080 SUP_DPRINTF(("supR3HardenedWinVerifyCacheProcessImportTodos: RTStrToUtf16Ex #1 failed: %Rrc\n", rc));
1081
1082 /*
1083 * If not something that gets remapped, do the half normal searching we need.
1084 */
1085 if (hFile == INVALID_HANDLE_VALUE)
1086 {
1087 struct
1088 {
1089 PRTUTF16 pawcDir;
1090 uint32_t cwcDir;
1091 } Tmp, aDirs[] =
1092 {
1093 { g_System32NtPath.UniStr.Buffer, g_System32NtPath.UniStr.Length / sizeof(WCHAR) },
1094 { g_SupLibHardenedExeNtPath.UniStr.Buffer, g_SupLibHardenedAppBinNtPath.UniStr.Length / sizeof(WCHAR) },
1095 { pCur->pwszAltSearchDir, pCur->cwcAltSearchDir },
1096 };
1097
1098 /* Search System32 first, unless it's a 'V*' or 'm*' name, the latter for msvcrt. */
1099 if ( pCur->szName[0] == 'v'
1100 || pCur->szName[0] == 'V'
1101 || pCur->szName[0] == 'm'
1102 || pCur->szName[0] == 'M')
1103 {
1104 Tmp = aDirs[0];
1105 aDirs[0] = aDirs[1];
1106 aDirs[1] = Tmp;
1107 }
1108
1109 for (uint32_t i = 0; i < RT_ELEMENTS(aDirs); i++)
1110 {
1111 if (aDirs[i].pawcDir && aDirs[i].cwcDir && aDirs[i].cwcDir < RT_ELEMENTS(wszPath) / 3 * 2)
1112 {
1113 memcpy(wszPath, aDirs[i].pawcDir, aDirs[i].cwcDir * sizeof(RTUTF16));
1114 uint32_t cwc = aDirs[i].cwcDir;
1115 wszPath[cwc++] = '\\';
1116 cwcName = RT_ELEMENTS(wszPath) - cwc;
1117 pwszName = &wszPath[cwc];
1118 rc = RTStrToUtf16Ex(pCur->szName, RTSTR_MAX, &pwszName, cwcName, &cwcName);
1119 if (RT_SUCCESS(rc))
1120 {
1121 IO_STATUS_BLOCK Ios = RTNT_IO_STATUS_BLOCK_INITIALIZER;
1122 UNICODE_STRING NtName;
1123 NtName.Buffer = wszPath;
1124 NtName.Length = (USHORT)((cwc + cwcName) * sizeof(WCHAR));
1125 NtName.MaximumLength = NtName.Length + sizeof(WCHAR);
1126 OBJECT_ATTRIBUTES ObjAttr;
1127 InitializeObjectAttributes(&ObjAttr, &NtName, OBJ_CASE_INSENSITIVE, NULL /*hRootDir*/, NULL /*pSecDesc*/);
1128
1129 rcNt = NtCreateFile(&hFile,
1130 FILE_READ_DATA | READ_CONTROL | SYNCHRONIZE,
1131 &ObjAttr,
1132 &Ios,
1133 NULL /* Allocation Size*/,
1134 FILE_ATTRIBUTE_NORMAL,
1135 FILE_SHARE_READ,
1136 FILE_OPEN,
1137 FILE_NON_DIRECTORY_FILE | FILE_SYNCHRONOUS_IO_NONALERT,
1138 NULL /*EaBuffer*/,
1139 0 /*EaLength*/);
1140 if (NT_SUCCESS(rcNt))
1141 rcNt = Ios.Status;
1142 if (NT_SUCCESS(rcNt))
1143 break;
1144 hFile = INVALID_HANDLE_VALUE;
1145 }
1146 else
1147 SUP_DPRINTF(("supR3HardenedWinVerifyCacheProcessImportTodos: RTStrToUtf16Ex #2 failed: %Rrc\n", rc));
1148 }
1149 }
1150 }
1151
1152 /*
1153 * If we successfully opened it, verify it and cache the result.
1154 */
1155 if (hFile != INVALID_HANDLE_VALUE)
1156 {
1157 SUP_DPRINTF(("supR3HardenedWinVerifyCacheProcessImportTodos: '%s' -> '%ls' [rcNtRedir=%#x]\n",
1158 pCur->szName, wszPath, rcNtRedir));
1159
1160 ULONG fAccess = 0;
1161 ULONG fProtect = 0;
1162 bool fCallRealApi = false;
1163 rcNt = supR3HardenedScreenImage(hFile, true /*fImage*/, false /*fIgnoreArch*/, &fAccess, &fProtect,
1164 &fCallRealApi, "Imports", false /*fAvoidWinVerifyTrust*/, NULL /*pfQuiet*/);
1165 NtClose(hFile);
1166 }
1167 else
1168 SUP_DPRINTF(("supR3HardenedWinVerifyCacheProcessImportTodos: Failed to locate '%s'\n", pCur->szName));
1169 }
1170 else
1171 SUP_DPRINTF(("supR3HardenedWinVerifyCacheProcessImportTodos: '%s' is in the cache.\n", pCur->szName));
1172
1173 RTMemFree(pCur);
1174 } while (pTodo);
1175 }
1176}
1177
1178
1179/**
1180 * Processes the list of WinVerifyTrust todos.
1181 */
1182static void supR3HardenedWinVerifyCacheProcessWvtTodos(void)
1183{
1184 PVERIFIERCACHEENTRY pReschedule = NULL;
1185 PVERIFIERCACHEENTRY volatile *ppReschedLastNext = &pReschedule;
1186
1187 /*
1188 * Work until we've got nothing more todo.
1189 */
1190 for (;;)
1191 {
1192 if (!supHardenedWinIsWinVerifyTrustCallable())
1193 break;
1194 PVERIFIERCACHEENTRY pTodo = ASMAtomicXchgPtrT(&g_pVerifierCacheTodoWvt, NULL, PVERIFIERCACHEENTRY);
1195 if (!pTodo)
1196 break;
1197 do
1198 {
1199 PVERIFIERCACHEENTRY pCur = pTodo;
1200 pTodo = pTodo->pNextTodoWvt;
1201 pCur->pNextTodoWvt = NULL;
1202
1203 if ( !pCur->fWinVerifyTrust
1204 && RT_SUCCESS(pCur->rc))
1205 {
1206 bool fWinVerifyTrust = false;
1207 int rc = supHardenedWinVerifyImageTrust(pCur->hFile, pCur->wszPath, pCur->fFlags, pCur->rc,
1208 &fWinVerifyTrust, NULL /* pErrInfo*/);
1209 if (RT_FAILURE(rc) || fWinVerifyTrust)
1210 {
1211 SUP_DPRINTF(("supR3HardenedWinVerifyCacheProcessWvtTodos: %d (was %d) fWinVerifyTrust=%d for '%ls'\n",
1212 rc, pCur->rc, fWinVerifyTrust, pCur->wszPath));
1213 pCur->fWinVerifyTrust = true;
1214 pCur->rc = rc;
1215 }
1216 else
1217 {
1218 /* Retry it at a later time. */
1219 SUP_DPRINTF(("supR3HardenedWinVerifyCacheProcessWvtTodos: %d (was %d) fWinVerifyTrust=%d for '%ls' [rescheduled]\n",
1220 rc, pCur->rc, fWinVerifyTrust, pCur->wszPath));
1221 *ppReschedLastNext = pCur;
1222 ppReschedLastNext = &pCur->pNextTodoWvt;
1223 }
1224 }
1225 /* else: already processed. */
1226 } while (pTodo);
1227 }
1228
1229 /*
1230 * Anything to reschedule.
1231 */
1232 if (pReschedule)
1233 {
1234 do
1235 *ppReschedLastNext = g_pVerifierCacheTodoWvt;
1236 while (!ASMAtomicCmpXchgPtr(&g_pVerifierCacheTodoWvt, pReschedule, *ppReschedLastNext));
1237 }
1238}
1239
1240
1241/**
1242 * Translates VBox status code (from supHardenedWinVerifyImageTrust) to an NT
1243 * status.
1244 *
1245 * @returns NT status.
1246 * @param rc VBox status code.
1247 */
1248static NTSTATUS supR3HardenedScreenImageCalcStatus(int rc) RT_NOTHROW_DEF
1249{
1250 /* This seems to be what LdrLoadDll returns when loading a 32-bit DLL into
1251 a 64-bit process. At least here on windows 10 (2015-11-xx).
1252
1253 NtCreateSection probably returns something different, possibly a warning,
1254 we currently don't distinguish between the too, so we stick with the
1255 LdrLoadDll one as it's definitely an error.*/
1256 if (rc == VERR_LDR_ARCH_MISMATCH)
1257 return STATUS_INVALID_IMAGE_FORMAT;
1258
1259 return STATUS_TRUST_FAILURE;
1260}
1261
1262
1263/**
1264 * Screens an image file or file mapped with execute access.
1265 *
1266 * @returns NT status code.
1267 * @param hFile The file handle.
1268 * @param fImage Set if image file mapping being made
1269 * (NtCreateSection thing).
1270 * @param fIgnoreArch Using the DONT_RESOLVE_DLL_REFERENCES flag,
1271 * which also implies that DLL init / term code
1272 * isn't called, so the architecture should be
1273 * ignored.
1274 * @param pfAccess Pointer to the NtCreateSection access flags,
1275 * so we can modify them if necessary.
1276 * @param pfProtect Pointer to the NtCreateSection protection
1277 * flags, so we can modify them if necessary.
1278 * @param pfCallRealApi Whether it's ok to go on to the real API.
1279 * @param pszCaller Who is calling (for debugging / logging).
1280 * @param fAvoidWinVerifyTrust Whether we should avoid WinVerifyTrust.
1281 * @param pfQuiet Where to return whether to be quiet about
1282 * this image in the log (i.e. we've seen it
1283 * lots of times already). Optional.
1284 */
1285static NTSTATUS
1286supR3HardenedScreenImage(HANDLE hFile, bool fImage, bool fIgnoreArch, PULONG pfAccess, PULONG pfProtect,
1287 bool *pfCallRealApi, const char *pszCaller, bool fAvoidWinVerifyTrust, bool *pfQuiet) RT_NOTHROW_DEF
1288{
1289 *pfCallRealApi = false;
1290 if (pfQuiet)
1291 *pfQuiet = false;
1292
1293 /*
1294 * Query the name of the file, making sure to zero terminator the
1295 * string. (2nd half of buffer is used for error info, see below.)
1296 */
1297 union
1298 {
1299 UNICODE_STRING UniStr;
1300 uint8_t abBuffer[sizeof(UNICODE_STRING) + 2048 * sizeof(WCHAR)];
1301 } uBuf;
1302 RT_ZERO(uBuf);
1303 ULONG cbNameBuf;
1304 NTSTATUS rcNt = NtQueryObject(hFile, ObjectNameInformation, &uBuf, sizeof(uBuf) - sizeof(WCHAR) - 128, &cbNameBuf);
1305 if (!NT_SUCCESS(rcNt))
1306 {
1307 supR3HardenedError(VINF_SUCCESS, false,
1308 "supR3HardenedScreenImage/%s: NtQueryObject -> %#x (fImage=%d fProtect=%#x fAccess=%#x)\n",
1309 pszCaller, fImage, *pfProtect, *pfAccess);
1310 return rcNt;
1311 }
1312
1313 if (!RTNtPathFindPossible8dot3Name(uBuf.UniStr.Buffer))
1314 cbNameBuf += sizeof(WCHAR);
1315 else
1316 {
1317 uBuf.UniStr.MaximumLength = sizeof(uBuf) - 128;
1318 RTNtPathExpand8dot3Path(&uBuf.UniStr, true /*fPathOnly*/);
1319 cbNameBuf = (uintptr_t)uBuf.UniStr.Buffer + uBuf.UniStr.Length + sizeof(WCHAR) - (uintptr_t)&uBuf.abBuffer[0];
1320 }
1321
1322 /*
1323 * Check the cache.
1324 */
1325 PVERIFIERCACHEENTRY pCacheHit = supR3HardenedWinVerifyCacheLookup(&uBuf.UniStr, hFile);
1326 if (pCacheHit)
1327 {
1328 /* Do hit accounting and figure whether we need to be quiet or not. */
1329 uint32_t cHits = ASMAtomicIncU32(&pCacheHit->cHits);
1330 bool const fQuiet = cHits >= 8 && !RT_IS_POWER_OF_TWO(cHits);
1331 if (pfQuiet)
1332 *pfQuiet = fQuiet;
1333
1334 /* If we haven't done the WinVerifyTrust thing, do it if we can. */
1335 if ( !pCacheHit->fWinVerifyTrust
1336 && RT_SUCCESS(pCacheHit->rc)
1337 && supHardenedWinIsWinVerifyTrustCallable() )
1338 {
1339 if (!fAvoidWinVerifyTrust)
1340 {
1341 SUP_DPRINTF(("supR3HardenedScreenImage/%s: cache hit (%Rrc) on %ls [redoing WinVerifyTrust]\n",
1342 pszCaller, pCacheHit->rc, pCacheHit->wszPath));
1343
1344 bool fWinVerifyTrust = false;
1345 int rc = supHardenedWinVerifyImageTrust(pCacheHit->hFile, pCacheHit->wszPath, pCacheHit->fFlags, pCacheHit->rc,
1346 &fWinVerifyTrust, NULL /* pErrInfo*/);
1347 if (RT_FAILURE(rc) || fWinVerifyTrust)
1348 {
1349 SUP_DPRINTF(("supR3HardenedScreenImage/%s: %d (was %d) fWinVerifyTrust=%d for '%ls'\n",
1350 pszCaller, rc, pCacheHit->rc, fWinVerifyTrust, pCacheHit->wszPath));
1351 pCacheHit->fWinVerifyTrust = true;
1352 pCacheHit->rc = rc;
1353 }
1354 else
1355 SUP_DPRINTF(("supR3HardenedScreenImage/%s: WinVerifyTrust not available, rescheduling %ls\n",
1356 pszCaller, pCacheHit->wszPath));
1357 }
1358 else
1359 SUP_DPRINTF(("supR3HardenedScreenImage/%s: cache hit (%Rrc) on %ls [avoiding WinVerifyTrust]\n",
1360 pszCaller, pCacheHit->rc, pCacheHit->wszPath));
1361 }
1362 else if (!fQuiet || !pCacheHit->fWinVerifyTrust)
1363 SUP_DPRINTF(("supR3HardenedScreenImage/%s: cache hit (%Rrc) on %ls%s\n",
1364 pszCaller, pCacheHit->rc, pCacheHit->wszPath, pCacheHit->fWinVerifyTrust ? "" : " [lacks WinVerifyTrust]"));
1365
1366 /* Return the cached value. */
1367 if (RT_SUCCESS(pCacheHit->rc))
1368 {
1369 *pfCallRealApi = true;
1370 return STATUS_SUCCESS;
1371 }
1372
1373 if (!fQuiet)
1374 supR3HardenedError(VINF_SUCCESS, false,
1375 "supR3HardenedScreenImage/%s: cached rc=%Rrc fImage=%d fProtect=%#x fAccess=%#x cHits=%u %ls\n",
1376 pszCaller, pCacheHit->rc, fImage, *pfProtect, *pfAccess, cHits, uBuf.UniStr.Buffer);
1377 return supR3HardenedScreenImageCalcStatus(pCacheHit->rc);
1378 }
1379
1380 /*
1381 * On XP the loader might hand us handles with just FILE_EXECUTE and
1382 * SYNCHRONIZE, the means reading will fail later on. Also, we need
1383 * READ_CONTROL access to check the file ownership later on, and non
1384 * of the OS versions seems be giving us that. So, in effect we
1385 * more or less always reopen the file here.
1386 */
1387 HANDLE hMyFile = NULL;
1388 rcNt = NtDuplicateObject(NtCurrentProcess(), hFile, NtCurrentProcess(),
1389 &hMyFile,
1390 FILE_READ_DATA | READ_CONTROL | SYNCHRONIZE,
1391 0 /* Handle attributes*/, 0 /* Options */);
1392 if (!NT_SUCCESS(rcNt))
1393 {
1394 if (rcNt == STATUS_ACCESS_DENIED)
1395 {
1396 IO_STATUS_BLOCK Ios = RTNT_IO_STATUS_BLOCK_INITIALIZER;
1397 OBJECT_ATTRIBUTES ObjAttr;
1398 InitializeObjectAttributes(&ObjAttr, &uBuf.UniStr, OBJ_CASE_INSENSITIVE, NULL /*hRootDir*/, NULL /*pSecDesc*/);
1399
1400 rcNt = NtCreateFile(&hMyFile,
1401 FILE_READ_DATA | READ_CONTROL | SYNCHRONIZE,
1402 &ObjAttr,
1403 &Ios,
1404 NULL /* Allocation Size*/,
1405 FILE_ATTRIBUTE_NORMAL,
1406 FILE_SHARE_READ,
1407 FILE_OPEN,
1408 FILE_NON_DIRECTORY_FILE | FILE_SYNCHRONOUS_IO_NONALERT,
1409 NULL /*EaBuffer*/,
1410 0 /*EaLength*/);
1411 if (NT_SUCCESS(rcNt))
1412 rcNt = Ios.Status;
1413 if (!NT_SUCCESS(rcNt))
1414 {
1415 supR3HardenedError(VINF_SUCCESS, false,
1416 "supR3HardenedScreenImage/%s: Failed to duplicate and open the file: rcNt=%#x hFile=%p %ls\n",
1417 pszCaller, rcNt, hFile, uBuf.UniStr.Buffer);
1418 return rcNt;
1419 }
1420
1421 /* Check that we've got the same file. */
1422 LARGE_INTEGER idMyFile, idInFile;
1423 bool fMyValid = supR3HardenedWinVerifyCacheGetIndexNumber(hMyFile, &idMyFile);
1424 bool fInValid = supR3HardenedWinVerifyCacheGetIndexNumber(hFile, &idInFile);
1425 if ( fMyValid
1426 && ( fMyValid != fInValid
1427 || idMyFile.QuadPart != idInFile.QuadPart))
1428 {
1429 supR3HardenedError(VINF_SUCCESS, false,
1430 "supR3HardenedScreenImage/%s: Re-opened has different ID that input: %#llx vx %#llx (%ls)\n",
1431 pszCaller, rcNt, idMyFile.QuadPart, idInFile.QuadPart, uBuf.UniStr.Buffer);
1432 NtClose(hMyFile);
1433 return STATUS_TRUST_FAILURE;
1434 }
1435 }
1436 else
1437 {
1438 SUP_DPRINTF(("supR3HardenedScreenImage/%s: NtDuplicateObject -> %#x\n", pszCaller, rcNt));
1439#ifdef DEBUG
1440
1441 supR3HardenedError(VINF_SUCCESS, false,
1442 "supR3HardenedScreenImage/%s: NtDuplicateObject(,%#x,) failed: %#x\n", pszCaller, hFile, rcNt);
1443#endif
1444 hMyFile = hFile;
1445 }
1446 }
1447
1448 /*
1449 * Special Kludge for Windows XP and W2K3 and their stupid attempts
1450 * at mapping a hidden XML file called c:\Windows\WindowsShell.Manifest
1451 * with executable access. The image bit isn't set, fortunately.
1452 */
1453 if ( !fImage
1454 && uBuf.UniStr.Length > g_System32NtPath.UniStr.Length - sizeof(L"System32") + sizeof(WCHAR)
1455 && memcmp(uBuf.UniStr.Buffer, g_System32NtPath.UniStr.Buffer,
1456 g_System32NtPath.UniStr.Length - sizeof(L"System32") + sizeof(WCHAR)) == 0)
1457 {
1458 PRTUTF16 pwszName = &uBuf.UniStr.Buffer[(g_System32NtPath.UniStr.Length - sizeof(L"System32") + sizeof(WCHAR)) / sizeof(WCHAR)];
1459 if (RTUtf16ICmpAscii(pwszName, "WindowsShell.Manifest") == 0)
1460 {
1461 /*
1462 * Drop all executable access to the mapping and let it continue.
1463 */
1464 SUP_DPRINTF(("supR3HardenedScreenImage/%s: Applying the drop-exec-kludge for '%ls'\n", pszCaller, uBuf.UniStr.Buffer));
1465 if (*pfAccess & SECTION_MAP_EXECUTE)
1466 *pfAccess = (*pfAccess & ~SECTION_MAP_EXECUTE) | SECTION_MAP_READ;
1467 if (*pfProtect & PAGE_EXECUTE)
1468 *pfProtect = (*pfProtect & ~PAGE_EXECUTE) | PAGE_READONLY;
1469 *pfProtect = (*pfProtect & ~UINT32_C(0xf0)) | ((*pfProtect & UINT32_C(0xe0)) >> 4);
1470 if (hMyFile != hFile)
1471 NtClose(hMyFile);
1472 *pfCallRealApi = true;
1473 return STATUS_SUCCESS;
1474 }
1475 }
1476
1477#ifndef VBOX_PERMIT_EVEN_MORE
1478 /*
1479 * Check the path. We don't allow DLLs to be loaded from just anywhere:
1480 * 1. System32 - normal code or cat signing, owner TrustedInstaller/Administrators/LocalSystem.
1481 * 2. WinSxS - normal code or cat signing, owner TrustedInstaller/Administrators/LocalSystem.
1482 * 3. VirtualBox - build with:
1483 * - regular code signing cert: build cert code signing, owner TrustedInstaller/Administrators/LocalSystem.
1484 * - kernel code signing cert: kernel code signing and integrity checks.
1485 * 4. AppPatchDir - normal code or cat signing, owner TrustedInstaller/Administrators/LocalSystem.
1486 * 5. Program Files - normal code or cat signing, owner TrustedInstaller/Administrators/LocalSystem.
1487 * 6. Common Files - normal code or cat signing, owner TrustedInstaller/Administrators/LocalSystem.
1488 * 7. x86 variations of 4 & 5 - ditto.
1489 *
1490 * Note! VBOX_WITHOUT_KERNEL_CODE_SIGNING_CERT means the /IntegrityCheck does
1491 * work as it doesn't seems like MS has come up with a generally accessible
1492 * alternative to the expired kernel code signing scheme for using this
1493 * securty enhancement.
1494 */
1495 uint32_t fFlags = 0;
1496 if (supHardViUniStrPathStartsWithUniStr(&uBuf.UniStr, &g_System32NtPath.UniStr, true /*fCheckSlash*/))
1497 fFlags |= SUPHNTVI_F_ALLOW_CAT_FILE_VERIFICATION | SUPHNTVI_F_TRUSTED_INSTALLER_OR_SIMILAR_OWNER;
1498 else if (supHardViUniStrPathStartsWithUniStr(&uBuf.UniStr, &g_WinSxSNtPath.UniStr, true /*fCheckSlash*/))
1499 fFlags |= SUPHNTVI_F_ALLOW_CAT_FILE_VERIFICATION | SUPHNTVI_F_TRUSTED_INSTALLER_OR_SIMILAR_OWNER;
1500 else if (supHardViUniStrPathStartsWithUniStr(&uBuf.UniStr, &g_SupLibHardenedAppBinNtPath.UniStr, true /*fCheckSlash*/))
1501# ifdef VBOX_WITHOUT_WINDOWS_KERNEL_CODE_SIGNING_CERT
1502 /** @todo r=bird: See SUPHNTVI_F_REQUIRE_BUILD_CERT comment below (in the
1503 * code that's actually used). */
1504 fFlags |= SUPHNTVI_F_REQUIRE_BUILD_CERT | SUPHNTVI_F_TRUSTED_INSTALLER_OR_SIMILAR_OWNER;
1505# else
1506 fFlags |= SUPHNTVI_F_REQUIRE_KERNEL_CODE_SIGNING | SUPHNTVI_F_REQUIRE_SIGNATURE_ENFORCEMENT;
1507# endif
1508# ifdef VBOX_PERMIT_MORE
1509 else if (supHardViIsAppPatchDir(uBuf.UniStr.Buffer, uBuf.UniStr.Length / sizeof(WCHAR)))
1510 fFlags |= SUPHNTVI_F_ALLOW_CAT_FILE_VERIFICATION | SUPHNTVI_F_TRUSTED_INSTALLER_OR_SIMILAR_OWNER;
1511 else if (supHardViUniStrPathStartsWithUniStr(&uBuf.UniStr, &g_ProgramFilesNtPath.UniStr, true /*fCheckSlash*/))
1512 fFlags |= SUPHNTVI_F_ALLOW_CAT_FILE_VERIFICATION | SUPHNTVI_F_TRUSTED_INSTALLER_OR_SIMILAR_OWNER;
1513 else if (supHardViUniStrPathStartsWithUniStr(&uBuf.UniStr, &g_CommonFilesNtPath.UniStr, true /*fCheckSlash*/))
1514 fFlags |= SUPHNTVI_F_ALLOW_CAT_FILE_VERIFICATION | SUPHNTVI_F_TRUSTED_INSTALLER_OR_SIMILAR_OWNER;
1515# ifdef RT_ARCH_AMD64
1516 else if (supHardViUniStrPathStartsWithUniStr(&uBuf.UniStr, &g_ProgramFilesX86NtPath.UniStr, true /*fCheckSlash*/))
1517 fFlags |= SUPHNTVI_F_ALLOW_CAT_FILE_VERIFICATION | SUPHNTVI_F_TRUSTED_INSTALLER_OR_SIMILAR_OWNER;
1518 else if (supHardViUniStrPathStartsWithUniStr(&uBuf.UniStr, &g_CommonFilesX86NtPath.UniStr, true /*fCheckSlash*/))
1519 fFlags |= SUPHNTVI_F_ALLOW_CAT_FILE_VERIFICATION | SUPHNTVI_F_TRUSTED_INSTALLER_OR_SIMILAR_OWNER;
1520# endif
1521# endif
1522# ifdef VBOX_PERMIT_VISUAL_STUDIO_PROFILING
1523 /* Hack to allow profiling our code with Visual Studio. */
1524 else if ( uBuf.UniStr.Length > sizeof(L"\\SamplingRuntime.dll")
1525 && memcmp(uBuf.UniStr.Buffer + (uBuf.UniStr.Length - sizeof(L"\\SamplingRuntime.dll") + sizeof(WCHAR)) / sizeof(WCHAR),
1526 L"\\SamplingRuntime.dll", sizeof(L"\\SamplingRuntime.dll") - sizeof(WCHAR)) == 0 )
1527 {
1528 if (hMyFile != hFile)
1529 NtClose(hMyFile);
1530 *pfCallRealApi = true;
1531 return STATUS_SUCCESS;
1532 }
1533# endif
1534 else
1535 {
1536 supR3HardenedError(VINF_SUCCESS, false,
1537 "supR3HardenedScreenImage/%s: Not a trusted location: '%ls' (fImage=%d fProtect=%#x fAccess=%#x)\n",
1538 pszCaller, uBuf.UniStr.Buffer, fImage, *pfAccess, *pfProtect);
1539 if (hMyFile != hFile)
1540 NtClose(hMyFile);
1541 return STATUS_TRUST_FAILURE;
1542 }
1543
1544#else /* VBOX_PERMIT_EVEN_MORE */
1545 /*
1546 * Require trusted installer + some kind of signature on everything, except
1547 * for the VBox bits where we have extra requirements depending on the signing
1548 * certificate used:
1549 * - regular code signing cert: build cert code signing, owner TrustedInstaller/Administrators/LocalSystem.
1550 * - kernel code signing cert: kernel code signing and integrity checks.
1551 */
1552 uint32_t fFlags = 0;
1553 if (supHardViUniStrPathStartsWithUniStr(&uBuf.UniStr, &g_SupLibHardenedAppBinNtPath.UniStr, true /*fCheckSlash*/))
1554# ifdef VBOX_WITHOUT_WINDOWS_KERNEL_CODE_SIGNING_CERT
1555 /** @todo r=bird: Since extension packs are installed under
1556 * g_SupLibHardenedAppBinNtPath and I'm pretty sure that everything loaded into
1557 * a VBox VM process goes thru this validation step at DLL load time, this means
1558 * only we can now sign extension packs.
1559 *
1560 * I suspect we have to relax the signing restrictions on the ExtensionPacks
1561 * subdirectory to keep 3rd party extensions working. */
1562 fFlags |= SUPHNTVI_F_REQUIRE_BUILD_CERT | SUPHNTVI_F_TRUSTED_INSTALLER_OR_SIMILAR_OWNER;
1563# else
1564 fFlags |= SUPHNTVI_F_REQUIRE_KERNEL_CODE_SIGNING | SUPHNTVI_F_REQUIRE_SIGNATURE_ENFORCEMENT;
1565# endif
1566 else
1567 fFlags |= SUPHNTVI_F_ALLOW_CAT_FILE_VERIFICATION | SUPHNTVI_F_TRUSTED_INSTALLER_OR_SIMILAR_OWNER;
1568#endif /* VBOX_PERMIT_EVEN_MORE */
1569
1570 /*
1571 * Do the verification. For better error message we borrow what's
1572 * left of the path buffer for an RTERRINFO buffer.
1573 */
1574 if (fIgnoreArch)
1575 fFlags |= SUPHNTVI_F_IGNORE_ARCHITECTURE;
1576 RTERRINFO ErrInfo;
1577 RTErrInfoInit(&ErrInfo, (char *)&uBuf.abBuffer[cbNameBuf], sizeof(uBuf) - cbNameBuf);
1578
1579 int rc;
1580 bool fWinVerifyTrust = false;
1581 rc = supHardenedWinVerifyImageByHandle(hMyFile, uBuf.UniStr.Buffer, fFlags, fAvoidWinVerifyTrust, &fWinVerifyTrust, &ErrInfo);
1582 if (RT_FAILURE(rc))
1583 {
1584 supR3HardenedError(VINF_SUCCESS, false,
1585 "supR3HardenedScreenImage/%s: rc=%Rrc fImage=%d fProtect=%#x fAccess=%#x %ls: %s\n",
1586 pszCaller, rc, fImage, *pfAccess, *pfProtect, uBuf.UniStr.Buffer, ErrInfo.pszMsg);
1587 if (hMyFile != hFile)
1588 supR3HardenedWinVerifyCacheInsert(&uBuf.UniStr, hMyFile, rc, fWinVerifyTrust, fFlags);
1589 return supR3HardenedScreenImageCalcStatus(rc);
1590 }
1591
1592 /*
1593 * Insert into the cache.
1594 */
1595 if (hMyFile != hFile)
1596 supR3HardenedWinVerifyCacheInsert(&uBuf.UniStr, hMyFile, rc, fWinVerifyTrust, fFlags);
1597
1598 *pfCallRealApi = true;
1599 return STATUS_SUCCESS;
1600}
1601
1602
1603/**
1604 * Preloads a file into the verify cache if possible.
1605 *
1606 * This is used to avoid known cyclic LoadLibrary issues with WinVerifyTrust.
1607 *
1608 * @param pwszName The name of the DLL to verify.
1609 */
1610DECLHIDDEN(void) supR3HardenedWinVerifyCachePreload(PCRTUTF16 pwszName)
1611{
1612 HANDLE hFile = RTNT_INVALID_HANDLE_VALUE;
1613 IO_STATUS_BLOCK Ios = RTNT_IO_STATUS_BLOCK_INITIALIZER;
1614
1615 UNICODE_STRING UniStr;
1616 UniStr.Buffer = (PWCHAR)pwszName;
1617 UniStr.Length = (USHORT)(RTUtf16Len(pwszName) * sizeof(WCHAR));
1618 UniStr.MaximumLength = UniStr.Length + sizeof(WCHAR);
1619
1620 OBJECT_ATTRIBUTES ObjAttr;
1621 InitializeObjectAttributes(&ObjAttr, &UniStr, OBJ_CASE_INSENSITIVE, NULL /*hRootDir*/, NULL /*pSecDesc*/);
1622
1623 NTSTATUS rcNt = NtCreateFile(&hFile,
1624 FILE_READ_DATA | READ_CONTROL | SYNCHRONIZE,
1625 &ObjAttr,
1626 &Ios,
1627 NULL /* Allocation Size*/,
1628 FILE_ATTRIBUTE_NORMAL,
1629 FILE_SHARE_READ,
1630 FILE_OPEN,
1631 FILE_NON_DIRECTORY_FILE | FILE_SYNCHRONOUS_IO_NONALERT,
1632 NULL /*EaBuffer*/,
1633 0 /*EaLength*/);
1634 if (NT_SUCCESS(rcNt))
1635 rcNt = Ios.Status;
1636 if (!NT_SUCCESS(rcNt))
1637 {
1638 SUP_DPRINTF(("supR3HardenedWinVerifyCachePreload: Error %#x opening '%ls'.\n", rcNt, pwszName));
1639 return;
1640 }
1641
1642 ULONG fAccess = 0;
1643 ULONG fProtect = 0;
1644 bool fCallRealApi;
1645 //SUP_DPRINTF(("supR3HardenedWinVerifyCachePreload: scanning %ls\n", pwszName));
1646 supR3HardenedScreenImage(hFile, false, false /*fIgnoreArch*/, &fAccess, &fProtect, &fCallRealApi, "preload",
1647 false /*fAvoidWinVerifyTrust*/, NULL /*pfQuiet*/);
1648 //SUP_DPRINTF(("supR3HardenedWinVerifyCachePreload: done %ls\n", pwszName));
1649
1650 NtClose(hFile);
1651}
1652
1653
1654
1655/**
1656 * Hook that monitors NtCreateSection calls.
1657 *
1658 * @returns NT status code.
1659 * @param phSection Where to return the section handle.
1660 * @param fAccess The desired access.
1661 * @param pObjAttribs The object attributes (optional).
1662 * @param pcbSection The section size (optional).
1663 * @param fProtect The max section protection.
1664 * @param fAttribs The section attributes.
1665 * @param hFile The file to create a section from (optional).
1666 */
1667__declspec(guard(ignore)) /* don't barf when calling g_pfnNtCreateSectionReal */
1668static NTSTATUS NTAPI
1669supR3HardenedMonitor_NtCreateSection(PHANDLE phSection, ACCESS_MASK fAccess, POBJECT_ATTRIBUTES pObjAttribs,
1670 PLARGE_INTEGER pcbSection, ULONG fProtect, ULONG fAttribs, HANDLE hFile)
1671{
1672 bool fNeedUncChecking = false;
1673 if ( hFile != NULL
1674 && hFile != INVALID_HANDLE_VALUE)
1675 {
1676 bool const fImage = RT_BOOL(fAttribs & (SEC_IMAGE | SEC_PROTECTED_IMAGE));
1677 bool const fExecMap = RT_BOOL(fAccess & SECTION_MAP_EXECUTE);
1678 bool const fExecProt = RT_BOOL(fProtect & (PAGE_EXECUTE | PAGE_EXECUTE_READ | PAGE_EXECUTE_WRITECOPY
1679 | PAGE_EXECUTE_READWRITE));
1680 if (fImage || fExecMap || fExecProt)
1681 {
1682 fNeedUncChecking = true;
1683 DWORD dwSavedLastError = RtlGetLastWin32Error();
1684
1685 bool fCallRealApi;
1686 //SUP_DPRINTF(("supR3HardenedMonitor_NtCreateSection: 1\n"));
1687 NTSTATUS rcNt = supR3HardenedScreenImage(hFile, fImage, true /*fIgnoreArch*/, &fAccess, &fProtect, &fCallRealApi,
1688 "NtCreateSection", true /*fAvoidWinVerifyTrust*/, NULL /*pfQuiet*/);
1689 //SUP_DPRINTF(("supR3HardenedMonitor_NtCreateSection: 2 rcNt=%#x fCallRealApi=%#x\n", rcNt, fCallRealApi));
1690
1691 RtlRestoreLastWin32Error(dwSavedLastError);
1692
1693 if (!NT_SUCCESS(rcNt))
1694 return rcNt;
1695 Assert(fCallRealApi);
1696 if (!fCallRealApi)
1697 return STATUS_TRUST_FAILURE;
1698
1699 }
1700 }
1701
1702 /*
1703 * Call checked out OK, call the original.
1704 */
1705 NTSTATUS rcNtReal = g_pfnNtCreateSectionReal(phSection, fAccess, pObjAttribs, pcbSection, fProtect, fAttribs, hFile);
1706
1707 /*
1708 * Check that the image that got mapped bear some resemblance to the one that was
1709 * requested. Apparently there are ways to trick the NT cache manager to map a
1710 * file different from hFile into memory using local UNC accesses.
1711 */
1712 if ( NT_SUCCESS(rcNtReal)
1713 && fNeedUncChecking)
1714 {
1715 DWORD dwSavedLastError = RtlGetLastWin32Error();
1716
1717 bool fOkay = false;
1718
1719 /* To get the name of the file backing the section, we unfortunately have to map it. */
1720 SIZE_T cbView = 0;
1721 PVOID pvTmpMap = NULL;
1722 NTSTATUS rcNt = NtMapViewOfSection(*phSection, NtCurrentProcess(), &pvTmpMap, 0, 0, NULL /*poffSection*/, &cbView,
1723 ViewUnmap, MEM_TOP_DOWN, PAGE_EXECUTE);
1724 if (NT_SUCCESS(rcNt))
1725 {
1726 /* Query the name. */
1727 union
1728 {
1729 UNICODE_STRING UniStr;
1730 RTUTF16 awcBuf[512];
1731 } uBuf;
1732 RT_ZERO(uBuf);
1733 SIZE_T cbActual = 0;
1734 NTSTATUS rcNtQuery = NtQueryVirtualMemory(NtCurrentProcess(), pvTmpMap, MemorySectionName,
1735 &uBuf, sizeof(uBuf) - sizeof(RTUTF16), &cbActual);
1736
1737 /* Unmap the view. */
1738 rcNt = NtUnmapViewOfSection(NtCurrentProcess(), pvTmpMap);
1739 if (!NT_SUCCESS(rcNt))
1740 SUP_DPRINTF(("supR3HardenedMonitor_NtCreateSection: NtUnmapViewOfSection failed on %p (hSection=%p, hFile=%p) with %#x!\n",
1741 pvTmpMap, *phSection, hFile, rcNt));
1742
1743 /* Process the name query result. */
1744 if (NT_SUCCESS(rcNtQuery))
1745 {
1746 static UNICODE_STRING const s_UncPrefix = RTNT_CONSTANT_UNISTR(L"\\Device\\Mup");
1747 if (!supHardViUniStrPathStartsWithUniStr(&uBuf.UniStr, &s_UncPrefix, true /*fCheckSlash*/))
1748 fOkay = true;
1749 else
1750 supR3HardenedError(VINF_SUCCESS, false,
1751 "supR3HardenedMonitor_NtCreateSection: Image section with UNC path is not trusted: '%.*ls'\n",
1752 uBuf.UniStr.Length / sizeof(RTUTF16), uBuf.UniStr.Buffer);
1753 }
1754 else
1755 SUP_DPRINTF(("supR3HardenedMonitor_NtCreateSection: NtQueryVirtualMemory failed on %p (hFile=%p) with %#x -> STATUS_TRUST_FAILURE\n",
1756 *phSection, hFile, rcNt));
1757 }
1758 else
1759 SUP_DPRINTF(("supR3HardenedMonitor_NtCreateSection: NtMapViewOfSection failed on %p (hFile=%p) with %#x -> STATUS_TRUST_FAILURE\n",
1760 *phSection, hFile, rcNt));
1761 if (!fOkay)
1762 {
1763 NtClose(*phSection);
1764 *phSection = INVALID_HANDLE_VALUE;
1765 RtlRestoreLastWin32Error(dwSavedLastError);
1766 return STATUS_TRUST_FAILURE;
1767 }
1768
1769 RtlRestoreLastWin32Error(dwSavedLastError);
1770 }
1771 return rcNtReal;
1772}
1773
1774
1775/**
1776 * Checks if the given name is a valid ApiSet name.
1777 *
1778 * This is only called on likely looking names.
1779 *
1780 * @returns true if ApiSet name, false if not.
1781 * @param pName The name to check out.
1782 */
1783static bool supR3HardenedIsApiSetDll(PUNICODE_STRING pName)
1784{
1785 /*
1786 * API added in Windows 8, or so they say.
1787 */
1788 if (ApiSetQueryApiSetPresence != NULL)
1789 {
1790 BOOLEAN fPresent = FALSE;
1791 NTSTATUS rcNt = ApiSetQueryApiSetPresence(pName, &fPresent);
1792 SUP_DPRINTF(("supR3HardenedIsApiSetDll: ApiSetQueryApiSetPresence(%.*ls) -> %#x, fPresent=%d\n",
1793 pName->Length / sizeof(WCHAR), pName->Buffer, rcNt, fPresent));
1794 return fPresent != 0;
1795 }
1796
1797 /*
1798 * Fallback needed for Windows 7. Fortunately, there aren't too many fake DLLs here.
1799 */
1800 if ( g_uNtVerCombined >= SUP_NT_VER_W70
1801 && ( supHardViUtf16PathStartsWithEx(pName->Buffer, pName->Length / sizeof(WCHAR),
1802 L"api-ms-win-", 11, false /*fCheckSlash*/)
1803 || supHardViUtf16PathStartsWithEx(pName->Buffer, pName->Length / sizeof(WCHAR),
1804 L"ext-ms-win-", 11, false /*fCheckSlash*/) ))
1805 {
1806#define MY_ENTRY(a) { a, sizeof(a) - 1 }
1807 static const struct { const char *psz; size_t cch; } s_aKnownSets[] =
1808 {
1809 MY_ENTRY("api-ms-win-core-console-l1-1-0 "),
1810 MY_ENTRY("api-ms-win-core-datetime-l1-1-0"),
1811 MY_ENTRY("api-ms-win-core-debug-l1-1-0"),
1812 MY_ENTRY("api-ms-win-core-delayload-l1-1-0"),
1813 MY_ENTRY("api-ms-win-core-errorhandling-l1-1-0"),
1814 MY_ENTRY("api-ms-win-core-fibers-l1-1-0"),
1815 MY_ENTRY("api-ms-win-core-file-l1-1-0"),
1816 MY_ENTRY("api-ms-win-core-handle-l1-1-0"),
1817 MY_ENTRY("api-ms-win-core-heap-l1-1-0"),
1818 MY_ENTRY("api-ms-win-core-interlocked-l1-1-0"),
1819 MY_ENTRY("api-ms-win-core-io-l1-1-0"),
1820 MY_ENTRY("api-ms-win-core-libraryloader-l1-1-0"),
1821 MY_ENTRY("api-ms-win-core-localization-l1-1-0"),
1822 MY_ENTRY("api-ms-win-core-localregistry-l1-1-0"),
1823 MY_ENTRY("api-ms-win-core-memory-l1-1-0"),
1824 MY_ENTRY("api-ms-win-core-misc-l1-1-0"),
1825 MY_ENTRY("api-ms-win-core-namedpipe-l1-1-0"),
1826 MY_ENTRY("api-ms-win-core-processenvironment-l1-1-0"),
1827 MY_ENTRY("api-ms-win-core-processthreads-l1-1-0"),
1828 MY_ENTRY("api-ms-win-core-profile-l1-1-0"),
1829 MY_ENTRY("api-ms-win-core-rtlsupport-l1-1-0"),
1830 MY_ENTRY("api-ms-win-core-string-l1-1-0"),
1831 MY_ENTRY("api-ms-win-core-synch-l1-1-0"),
1832 MY_ENTRY("api-ms-win-core-sysinfo-l1-1-0"),
1833 MY_ENTRY("api-ms-win-core-threadpool-l1-1-0"),
1834 MY_ENTRY("api-ms-win-core-ums-l1-1-0"),
1835 MY_ENTRY("api-ms-win-core-util-l1-1-0"),
1836 MY_ENTRY("api-ms-win-core-xstate-l1-1-0"),
1837 MY_ENTRY("api-ms-win-security-base-l1-1-0"),
1838 MY_ENTRY("api-ms-win-security-lsalookup-l1-1-0"),
1839 MY_ENTRY("api-ms-win-security-sddl-l1-1-0"),
1840 MY_ENTRY("api-ms-win-service-core-l1-1-0"),
1841 MY_ENTRY("api-ms-win-service-management-l1-1-0"),
1842 MY_ENTRY("api-ms-win-service-management-l2-1-0"),
1843 MY_ENTRY("api-ms-win-service-winsvc-l1-1-0"),
1844 };
1845#undef MY_ENTRY
1846
1847 /* drop the dll suffix if present. */
1848 PCRTUTF16 pawcName = pName->Buffer;
1849 size_t cwcName = pName->Length / sizeof(WCHAR);
1850 if ( cwcName > 5
1851 && (pawcName[cwcName - 1] == 'l' || pawcName[cwcName - 1] == 'L')
1852 && (pawcName[cwcName - 2] == 'l' || pawcName[cwcName - 2] == 'L')
1853 && (pawcName[cwcName - 3] == 'd' || pawcName[cwcName - 3] == 'D')
1854 && pawcName[cwcName - 4] == '.')
1855 cwcName -= 4;
1856
1857 /* Search the table. */
1858 for (size_t i = 0; i < RT_ELEMENTS(s_aKnownSets); i++)
1859 if ( cwcName == s_aKnownSets[i].cch
1860 && RTUtf16NICmpAscii(pawcName, s_aKnownSets[i].psz, cwcName) == 0)
1861 {
1862 SUP_DPRINTF(("supR3HardenedIsApiSetDll: '%.*ls' -> true\n", pName->Length / sizeof(WCHAR)));
1863 return true;
1864 }
1865
1866 SUP_DPRINTF(("supR3HardenedIsApiSetDll: Warning! '%.*ls' looks like an API set, but it's not in the list!\n",
1867 pName->Length / sizeof(WCHAR), pName->Buffer));
1868 }
1869
1870 SUP_DPRINTF(("supR3HardenedIsApiSetDll: '%.*ls' -> false\n", pName->Length / sizeof(WCHAR)));
1871 return false;
1872}
1873
1874
1875/**
1876 * Checks whether the given unicode string contains a path separator and at
1877 * least one dash.
1878 *
1879 * This is used to check for likely ApiSet name. So far, all the pseudo DLL
1880 * names include multiple dashes, so we use that as a criteria for recognizing
1881 * them. By happy coincident, most regular DLLs doesn't include dashes.
1882 *
1883 * @returns true if it contains path separator, false if only a name.
1884 * @param pPath The path to check.
1885 */
1886static bool supR3HardenedHasDashButNoPath(PUNICODE_STRING pPath)
1887{
1888 size_t cDashes = 0;
1889 size_t cwcLeft = pPath->Length / sizeof(WCHAR);
1890 PCRTUTF16 pwc = pPath->Buffer;
1891 while (cwcLeft-- > 0)
1892 {
1893 RTUTF16 wc = *pwc++;
1894 switch (wc)
1895 {
1896 default:
1897 break;
1898
1899 case '-':
1900 cDashes++;
1901 break;
1902
1903 case '\\':
1904 case '/':
1905 case ':':
1906 return false;
1907 }
1908 }
1909 return cDashes > 0;
1910}
1911
1912
1913/**
1914 * Helper for supR3HardenedMonitor_LdrLoadDll.
1915 *
1916 * @returns NT status code.
1917 * @param pwszPath The path destination buffer.
1918 * @param cwcPath The size of the path buffer.
1919 * @param pUniStrResult The result string.
1920 * @param pOrgName The orignal name (for errors).
1921 * @param pcwc Where to return the actual length.
1922 */
1923static NTSTATUS supR3HardenedCopyRedirectionResult(WCHAR *pwszPath, size_t cwcPath, PUNICODE_STRING pUniStrResult,
1924 PUNICODE_STRING pOrgName, UINT *pcwc)
1925{
1926 UINT cwc;
1927 *pcwc = cwc = pUniStrResult->Length / sizeof(WCHAR);
1928 if (pUniStrResult->Buffer == pwszPath)
1929 pwszPath[cwc] = '\0';
1930 else
1931 {
1932 if (cwc > cwcPath - 1)
1933 {
1934 supR3HardenedError(VINF_SUCCESS, false,
1935 "supR3HardenedMonitor_LdrLoadDll: Name too long: %.*ls -> %.*ls (RtlDosApplyFileIoslationRedirection_Ustr)\n",
1936 pOrgName->Length / sizeof(WCHAR), pOrgName->Buffer,
1937 pUniStrResult->Length / sizeof(WCHAR), pUniStrResult->Buffer);
1938 return STATUS_NAME_TOO_LONG;
1939 }
1940 memcpy(&pwszPath[0], pUniStrResult->Buffer, pUniStrResult->Length);
1941 pwszPath[cwc] = '\0';
1942 }
1943 return STATUS_SUCCESS;
1944}
1945
1946
1947/**
1948 * Helper for supR3HardenedMonitor_LdrLoadDll that compares the name part of the
1949 * input path against a ASCII name string of a given length.
1950 *
1951 * @returns true if the name part matches
1952 * @param pPath The LdrLoadDll input path.
1953 * @param pszName The name to try match it with.
1954 * @param cchName The name length.
1955 */
1956static bool supR3HardenedIsFilenameMatchDll(PUNICODE_STRING pPath, const char *pszName, size_t cchName)
1957{
1958 if (pPath->Length < cchName * 2)
1959 return false;
1960 PCRTUTF16 pwszTmp = &pPath->Buffer[pPath->Length / sizeof(RTUTF16) - cchName];
1961 if ( pPath->Length != cchName
1962 && pwszTmp[-1] != '\\'
1963 && pwszTmp[-1] != '/')
1964 return false;
1965 return RTUtf16ICmpAscii(pwszTmp, pszName) == 0;
1966}
1967
1968
1969/**
1970 * Hooks that intercepts LdrLoadDll calls.
1971 *
1972 * Two purposes:
1973 * -# Enforce our own search path restrictions.
1974 * -# Prevalidate DLLs about to be loaded so we don't upset the loader data
1975 * by doing it from within the NtCreateSection hook (WinVerifyTrust
1976 * seems to be doing harm there on W7/32).
1977 *
1978 * @returns
1979 * @param pwszSearchPath The search path to use.
1980 * @param pfFlags Flags on input. DLL characteristics or something
1981 * on return?
1982 * @param pName The name of the module.
1983 * @param phMod Where the handle of the loaded DLL is to be
1984 * returned to the caller.
1985 */
1986__declspec(guard(ignore)) /* don't barf when calling g_pfnLdrLoadDllReal */
1987static NTSTATUS NTAPI
1988supR3HardenedMonitor_LdrLoadDll(PWSTR pwszSearchPath, PULONG pfFlags, PUNICODE_STRING pName, PHANDLE phMod)
1989{
1990 DWORD dwSavedLastError = RtlGetLastWin32Error();
1991 PUNICODE_STRING const pOrgName = pName;
1992 NTSTATUS rcNt;
1993
1994 /*
1995 * Make sure the DLL notification callback is registered. If we could, we
1996 * would've done this during early process init, but due to lack of heap
1997 * and uninitialized loader lock, it's not possible that early on.
1998 *
1999 * The callback protects our NtDll hooks from getting unhooked by
2000 * "friendly" fire from the AV crowd.
2001 */
2002 supR3HardenedWinRegisterDllNotificationCallback();
2003
2004 /*
2005 * Process WinVerifyTrust todo before and after.
2006 */
2007 supR3HardenedWinVerifyCacheProcessWvtTodos();
2008
2009 /*
2010 * Reject things we don't want to deal with.
2011 */
2012 if (!pName || pName->Length == 0)
2013 {
2014 supR3HardenedError(VINF_SUCCESS, false, "supR3HardenedMonitor_LdrLoadDll: name is NULL or have a zero length.\n");
2015 SUP_DPRINTF(("supR3HardenedMonitor_LdrLoadDll: returns rcNt=%#x (pName=%p)\n", STATUS_INVALID_PARAMETER, pName));
2016 RtlRestoreLastWin32Error(dwSavedLastError);
2017 return STATUS_INVALID_PARAMETER;
2018 }
2019 PCWCHAR const pawcOrgName = pName->Buffer;
2020 uint32_t const cwcOrgName = pName->Length / sizeof(WCHAR);
2021
2022 /*SUP_DPRINTF(("supR3HardenedMonitor_LdrLoadDll: pName=%.*ls *pfFlags=%#x pwszSearchPath=%p:%ls\n",
2023 (unsigned)pName->Length / sizeof(WCHAR), pName->Buffer, pfFlags ? *pfFlags : UINT32_MAX, pwszSearchPath,
2024 !((uintptr_t)pwszSearchPath & 1) && (uintptr_t)pwszSearchPath >= 0x2000U ? pwszSearchPath : L"<flags>"));*/
2025
2026 /*
2027 * Reject long paths that's close to the 260 limit without looking.
2028 */
2029 if (cwcOrgName > 256)
2030 {
2031 supR3HardenedError(VINF_SUCCESS, false, "supR3HardenedMonitor_LdrLoadDll: too long name: %#x bytes\n", pName->Length);
2032 SUP_DPRINTF(("supR3HardenedMonitor_LdrLoadDll: returns rcNt=%#x\n", STATUS_NAME_TOO_LONG));
2033 RtlRestoreLastWin32Error(dwSavedLastError);
2034 return STATUS_NAME_TOO_LONG;
2035 }
2036
2037 /*
2038 * Reject all UNC-like paths as we cannot trust non-local files at all.
2039 * Note! We may have to relax this to deal with long path specifications and NT pass thrus.
2040 */
2041 if ( cwcOrgName >= 3
2042 && RTPATH_IS_SLASH(pawcOrgName[0])
2043 && RTPATH_IS_SLASH(pawcOrgName[1])
2044 && !RTPATH_IS_SLASH(pawcOrgName[2]))
2045 {
2046 supR3HardenedError(VINF_SUCCESS, false, "supR3HardenedMonitor_LdrLoadDll: rejecting UNC name '%.*ls'\n", cwcOrgName, pawcOrgName);
2047 SUP_DPRINTF(("supR3HardenedMonitor_LdrLoadDll: returns rcNt=%#x\n", STATUS_REDIRECTOR_NOT_STARTED));
2048 RtlRestoreLastWin32Error(dwSavedLastError);
2049 return STATUS_REDIRECTOR_NOT_STARTED;
2050 }
2051
2052 /*
2053 * Reject PGHook.dll as it creates a thread from its DllMain that breaks
2054 * our preconditions respawning the 2nd process, resulting in
2055 * VERR_SUP_VP_THREAD_NOT_ALONE. The DLL is being loaded by a user APC
2056 * scheduled during kernel32.dll load notification from a kernel driver,
2057 * so failing the load attempt should not upset anyone.
2058 */
2059 if (g_enmSupR3HardenedMainState == SUPR3HARDENEDMAINSTATE_WIN_EARLY_STUB_DEVICE_OPENED)
2060 {
2061 static const struct { const char *psz; size_t cch; } s_aUnwantedEarlyDlls[] =
2062 {
2063 { RT_STR_TUPLE("PGHook.dll") },
2064 };
2065 for (unsigned i = 0; i < RT_ELEMENTS(s_aUnwantedEarlyDlls); i++)
2066 if (supR3HardenedIsFilenameMatchDll(pName, s_aUnwantedEarlyDlls[i].psz, s_aUnwantedEarlyDlls[i].cch))
2067 {
2068 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",
2069 pName->Length / sizeof(RTUTF16), pName->Buffer));
2070 return STATUS_TOO_MANY_THREADS;
2071 }
2072 }
2073
2074 /*
2075 * Resolve the path, copying the result into wszPath
2076 */
2077 NTSTATUS rcNtResolve = STATUS_SUCCESS;
2078 bool fSkipValidation = false;
2079 bool fCheckIfLoaded = false;
2080 WCHAR wszPath[260];
2081 static UNICODE_STRING const s_DefaultSuffix = RTNT_CONSTANT_UNISTR(L".dll");
2082 UNICODE_STRING UniStrStatic = { 0, (USHORT)sizeof(wszPath) - sizeof(WCHAR), wszPath };
2083 UNICODE_STRING UniStrDynamic = { 0, 0, NULL };
2084 PUNICODE_STRING pUniStrResult = NULL;
2085 UNICODE_STRING ResolvedName;
2086
2087 /*
2088 * Process the name a little, checking if it needs a DLL suffix and is pathless.
2089 */
2090 uint32_t offLastSlash = UINT32_MAX;
2091 uint32_t offLastDot = UINT32_MAX;
2092 for (uint32_t i = 0; i < cwcOrgName; i++)
2093 switch (pawcOrgName[i])
2094 {
2095 case '\\':
2096 case '/':
2097 offLastSlash = i;
2098 offLastDot = UINT32_MAX;
2099 break;
2100 case '.':
2101 offLastDot = i;
2102 break;
2103 }
2104 bool const fNeedDllSuffix = offLastDot == UINT32_MAX;
2105 //bool const fTrailingDot = offLastDot == cwcOrgName - 1;
2106
2107 /*
2108 * Absolute path?
2109 */
2110 if ( ( cwcOrgName >= 4
2111 && RT_C_IS_ALPHA(pawcOrgName[0])
2112 && pawcOrgName[1] == ':'
2113 && RTPATH_IS_SLASH(pawcOrgName[2]) )
2114 || ( cwcOrgName >= 1
2115 && RTPATH_IS_SLASH(pawcOrgName[0]) )
2116 )
2117 {
2118 rcNtResolve = RtlDosApplyFileIsolationRedirection_Ustr(1 /*fFlags*/,
2119 pName,
2120 (PUNICODE_STRING)&s_DefaultSuffix,
2121 &UniStrStatic,
2122 &UniStrDynamic,
2123 &pUniStrResult,
2124 NULL /*pNewFlags*/,
2125 NULL /*pcbFilename*/,
2126 NULL /*pcbNeeded*/);
2127 if (NT_SUCCESS(rcNtResolve))
2128 {
2129 UINT cwc;
2130 rcNt = supR3HardenedCopyRedirectionResult(wszPath, RT_ELEMENTS(wszPath), pUniStrResult, pName, &cwc);
2131 RtlFreeUnicodeString(&UniStrDynamic);
2132 if (!NT_SUCCESS(rcNt))
2133 {
2134 SUP_DPRINTF(("supR3HardenedMonitor_LdrLoadDll: returns rcNt=%#x\n", rcNt));
2135 RtlRestoreLastWin32Error(dwSavedLastError);
2136 return rcNt;
2137 }
2138
2139 ResolvedName.Buffer = wszPath;
2140 ResolvedName.Length = (USHORT)(cwc * sizeof(WCHAR));
2141 ResolvedName.MaximumLength = ResolvedName.Length + sizeof(WCHAR);
2142
2143 SUP_DPRINTF(("supR3HardenedMonitor_LdrLoadDll: '%.*ls' -> '%.*ls' [redir]\n",
2144 (unsigned)pName->Length / sizeof(WCHAR), pName->Buffer,
2145 ResolvedName.Length / sizeof(WCHAR), ResolvedName.Buffer, rcNt));
2146 pName = &ResolvedName;
2147 }
2148 else
2149 {
2150 /* Copy the path. */
2151 memcpy(wszPath, pawcOrgName, cwcOrgName * sizeof(WCHAR));
2152 if (!fNeedDllSuffix)
2153 wszPath[cwcOrgName] = '\0';
2154 else
2155 {
2156 if (cwcOrgName + 4 >= RT_ELEMENTS(wszPath))
2157 {
2158 supR3HardenedError(VINF_SUCCESS, false,
2159 "supR3HardenedMonitor_LdrLoadDll: Name too long (abs): %.*ls\n", cwcOrgName, pawcOrgName);
2160 SUP_DPRINTF(("supR3HardenedMonitor_LdrLoadDll: returns rcNt=%#x\n", STATUS_NAME_TOO_LONG));
2161 RtlRestoreLastWin32Error(dwSavedLastError);
2162 return STATUS_NAME_TOO_LONG;
2163 }
2164 memcpy(&wszPath[cwcOrgName], L".dll", 5 * sizeof(WCHAR));
2165 }
2166 }
2167 }
2168 /*
2169 * Not an absolute path. Check if it's one of those special API set DLLs
2170 * or something we're known to use but should be taken from WinSxS.
2171 */
2172 else if ( supR3HardenedHasDashButNoPath(pName)
2173 && supR3HardenedIsApiSetDll(pName))
2174 {
2175 memcpy(wszPath, pName->Buffer, pName->Length);
2176 wszPath[pName->Length / sizeof(WCHAR)] = '\0';
2177 fSkipValidation = true;
2178 }
2179 /*
2180 * Not an absolute path or special API set. There are two alternatives
2181 * now, either there is no path at all or there is a relative path. We
2182 * will resolve it to an absolute path in either case, failing the call
2183 * if we can't.
2184 */
2185 else
2186 {
2187 /*
2188 * Reject relative paths for now as they might be breakout attempts.
2189 */
2190 if (offLastSlash != UINT32_MAX)
2191 {
2192 supR3HardenedError(VINF_SUCCESS, false,
2193 "supR3HardenedMonitor_LdrLoadDll: relative name not permitted: %.*ls\n",
2194 cwcOrgName, pawcOrgName);
2195 SUP_DPRINTF(("supR3HardenedMonitor_LdrLoadDll: returns rcNt=%#x\n", STATUS_OBJECT_NAME_INVALID));
2196 RtlRestoreLastWin32Error(dwSavedLastError);
2197 return STATUS_OBJECT_NAME_INVALID;
2198 }
2199
2200 /*
2201 * Perform dll redirection to WinSxS such. We using an undocumented
2202 * API here, which as always is a bit risky... ASSUMES that the API
2203 * returns a full DOS path.
2204 */
2205 UINT cwc;
2206 rcNtResolve = RtlDosApplyFileIsolationRedirection_Ustr(1 /*fFlags*/,
2207 pName,
2208 (PUNICODE_STRING)&s_DefaultSuffix,
2209 &UniStrStatic,
2210 &UniStrDynamic,
2211 &pUniStrResult,
2212 NULL /*pNewFlags*/,
2213 NULL /*pcbFilename*/,
2214 NULL /*pcbNeeded*/);
2215 if (NT_SUCCESS(rcNtResolve))
2216 {
2217 rcNt = supR3HardenedCopyRedirectionResult(wszPath, RT_ELEMENTS(wszPath), pUniStrResult, pName, &cwc);
2218 RtlFreeUnicodeString(&UniStrDynamic);
2219 if (!NT_SUCCESS(rcNt))
2220 {
2221 SUP_DPRINTF(("supR3HardenedMonitor_LdrLoadDll: returns rcNt=%#x\n", rcNt));
2222 RtlRestoreLastWin32Error(dwSavedLastError);
2223 return rcNt;
2224 }
2225 }
2226 else
2227 {
2228 /*
2229 * Search for the DLL. Only System32 is allowed as the target of
2230 * a search on the API level, all VBox calls will have full paths.
2231 * If the DLL is not in System32, we will resort to check if it's
2232 * refering to an already loaded DLL (fCheckIfLoaded).
2233 */
2234 AssertCompile(sizeof(g_System32WinPath.awcBuffer) <= sizeof(wszPath));
2235 cwc = g_System32WinPath.UniStr.Length / sizeof(RTUTF16); Assert(cwc > 2);
2236 if (cwc + 1 + cwcOrgName + fNeedDllSuffix * 4 >= RT_ELEMENTS(wszPath))
2237 {
2238 supR3HardenedError(VINF_SUCCESS, false,
2239 "supR3HardenedMonitor_LdrLoadDll: Name too long (system32): %.*ls\n", cwcOrgName, pawcOrgName);
2240 SUP_DPRINTF(("supR3HardenedMonitor_LdrLoadDll: returns rcNt=%#x\n", STATUS_NAME_TOO_LONG));
2241 RtlRestoreLastWin32Error(dwSavedLastError);
2242 return STATUS_NAME_TOO_LONG;
2243 }
2244 memcpy(wszPath, g_System32WinPath.UniStr.Buffer, cwc * sizeof(RTUTF16));
2245 wszPath[cwc++] = '\\';
2246 memcpy(&wszPath[cwc], pawcOrgName, cwcOrgName * sizeof(WCHAR));
2247 cwc += cwcOrgName;
2248 if (!fNeedDllSuffix)
2249 wszPath[cwc] = '\0';
2250 else
2251 {
2252 memcpy(&wszPath[cwc], L".dll", 5 * sizeof(WCHAR));
2253 cwc += 4;
2254 }
2255 fCheckIfLoaded = true;
2256 }
2257
2258 ResolvedName.Buffer = wszPath;
2259 ResolvedName.Length = (USHORT)(cwc * sizeof(WCHAR));
2260 ResolvedName.MaximumLength = ResolvedName.Length + sizeof(WCHAR);
2261 pName = &ResolvedName;
2262 }
2263
2264#ifndef IN_SUP_R3_STATIC
2265 /*
2266 * Reject blacklisted DLLs based on input name.
2267 */
2268 for (unsigned i = 0; g_aSupNtViBlacklistedDlls[i].psz != NULL; i++)
2269 if (supR3HardenedIsFilenameMatchDll(pName, g_aSupNtViBlacklistedDlls[i].psz, g_aSupNtViBlacklistedDlls[i].cch))
2270 {
2271 SUP_DPRINTF(("supR3HardenedMonitor_LdrLoadDll: Refusing to load blacklisted DLL: '%.*ls'\n",
2272 pName->Length / sizeof(RTUTF16), pName->Buffer));
2273 RtlRestoreLastWin32Error(dwSavedLastError);
2274 return STATUS_TOO_MANY_THREADS;
2275 }
2276#endif
2277
2278 bool fQuiet = false;
2279 if (!fSkipValidation)
2280 {
2281 /*
2282 * Try open the file. If this fails, never mind, just pass it on to
2283 * the real API as we've replaced any searchable name with a full name
2284 * and the real API can come up with a fitting status code for it.
2285 */
2286 HANDLE hRootDir;
2287 UNICODE_STRING NtPathUniStr;
2288 int rc = RTNtPathFromWinUtf16Ex(&NtPathUniStr, &hRootDir, wszPath, RTSTR_MAX);
2289 if (RT_FAILURE(rc))
2290 {
2291 supR3HardenedError(rc, false,
2292 "supR3HardenedMonitor_LdrLoadDll: RTNtPathFromWinUtf16Ex failed on '%ls': %Rrc\n", wszPath, rc);
2293 SUP_DPRINTF(("supR3HardenedMonitor_LdrLoadDll: returns rcNt=%#x\n", STATUS_OBJECT_NAME_INVALID));
2294 RtlRestoreLastWin32Error(dwSavedLastError);
2295 return STATUS_OBJECT_NAME_INVALID;
2296 }
2297
2298 HANDLE hFile = RTNT_INVALID_HANDLE_VALUE;
2299 IO_STATUS_BLOCK Ios = RTNT_IO_STATUS_BLOCK_INITIALIZER;
2300 OBJECT_ATTRIBUTES ObjAttr;
2301 InitializeObjectAttributes(&ObjAttr, &NtPathUniStr, OBJ_CASE_INSENSITIVE, hRootDir, NULL /*pSecDesc*/);
2302
2303 rcNt = NtCreateFile(&hFile,
2304 FILE_READ_DATA | READ_CONTROL | SYNCHRONIZE,
2305 &ObjAttr,
2306 &Ios,
2307 NULL /* Allocation Size*/,
2308 FILE_ATTRIBUTE_NORMAL,
2309 FILE_SHARE_READ,
2310 FILE_OPEN,
2311 FILE_NON_DIRECTORY_FILE | FILE_SYNCHRONOUS_IO_NONALERT,
2312 NULL /*EaBuffer*/,
2313 0 /*EaLength*/);
2314 if (NT_SUCCESS(rcNt))
2315 rcNt = Ios.Status;
2316 if (NT_SUCCESS(rcNt))
2317 {
2318 ULONG fAccess = 0;
2319 ULONG fProtect = 0;
2320 bool fCallRealApi = false;
2321 rcNt = supR3HardenedScreenImage(hFile, true /*fImage*/, RT_VALID_PTR(pfFlags) && (*pfFlags & 0x2) /*fIgnoreArch*/,
2322 &fAccess, &fProtect, &fCallRealApi,
2323 "LdrLoadDll", false /*fAvoidWinVerifyTrust*/, &fQuiet);
2324 NtClose(hFile);
2325 if (!NT_SUCCESS(rcNt))
2326 {
2327 if (!fQuiet)
2328 {
2329 if (pOrgName != pName)
2330 supR3HardenedError(VINF_SUCCESS, false, "supR3HardenedMonitor_LdrLoadDll: rejecting '%ls': rcNt=%#x\n",
2331 wszPath, rcNt);
2332 else
2333 supR3HardenedError(VINF_SUCCESS, false, "supR3HardenedMonitor_LdrLoadDll: rejecting '%ls' (%.*ls): rcNt=%#x\n",
2334 wszPath, pOrgName->Length / sizeof(WCHAR), pOrgName->Buffer, rcNt);
2335 SUP_DPRINTF(("supR3HardenedMonitor_LdrLoadDll: returns rcNt=%#x '%ls'\n", rcNt, wszPath));
2336 }
2337 RtlRestoreLastWin32Error(dwSavedLastError);
2338 return rcNt;
2339 }
2340
2341 supR3HardenedWinVerifyCacheProcessImportTodos();
2342 }
2343 else
2344 {
2345 DWORD dwErr = RtlGetLastWin32Error();
2346
2347 /*
2348 * Deal with special case where the caller (first case was MS LifeCam)
2349 * is using LoadLibrary instead of GetModuleHandle to find a loaded DLL.
2350 */
2351 NTSTATUS rcNtGetDll = STATUS_SUCCESS;
2352 if ( fCheckIfLoaded
2353 && ( rcNt == STATUS_OBJECT_NAME_NOT_FOUND
2354 || rcNt == STATUS_OBJECT_PATH_NOT_FOUND))
2355 {
2356 rcNtGetDll = LdrGetDllHandle(NULL /*DllPath*/, NULL /*pfFlags*/, pOrgName, phMod);
2357 if (NT_SUCCESS(rcNtGetDll))
2358 {
2359 RTNtPathFree(&NtPathUniStr, &hRootDir);
2360 RtlRestoreLastWin32Error(dwSavedLastError);
2361 return rcNtGetDll;
2362 }
2363 }
2364
2365 SUP_DPRINTF(("supR3HardenedMonitor_LdrLoadDll: error opening '%ls': %u (NtPath=%.*ls; Input=%.*ls; rcNtGetDll=%#x\n",
2366 wszPath, dwErr, NtPathUniStr.Length / sizeof(RTUTF16), NtPathUniStr.Buffer,
2367 pOrgName->Length / sizeof(WCHAR), pOrgName->Buffer, rcNtGetDll));
2368
2369 RTNtPathFree(&NtPathUniStr, &hRootDir);
2370 RtlRestoreLastWin32Error(dwSavedLastError);
2371 SUP_DPRINTF(("supR3HardenedMonitor_LdrLoadDll: returns rcNt=%#x '%ls'\n", rcNt, wszPath));
2372 return rcNt;
2373 }
2374 RTNtPathFree(&NtPathUniStr, &hRootDir);
2375 }
2376
2377 /*
2378 * Screened successfully enough. Call the real thing.
2379 */
2380 if (!fQuiet)
2381 {
2382 if (pOrgName != pName)
2383 SUP_DPRINTF(("supR3HardenedMonitor_LdrLoadDll: pName=%.*ls (Input=%.*ls, rcNtResolve=%#x) *pfFlags=%#x pwszSearchPath=%p:%ls [calling]\n",
2384 (unsigned)pName->Length / sizeof(WCHAR), pName->Buffer,
2385 (unsigned)pOrgName->Length / sizeof(WCHAR), pOrgName->Buffer, rcNtResolve,
2386 pfFlags ? *pfFlags : UINT32_MAX, pwszSearchPath,
2387 !((uintptr_t)pwszSearchPath & 1) && (uintptr_t)pwszSearchPath >= 0x2000U ? pwszSearchPath : L"<flags>"));
2388 else
2389 SUP_DPRINTF(("supR3HardenedMonitor_LdrLoadDll: pName=%.*ls (rcNtResolve=%#x) *pfFlags=%#x pwszSearchPath=%p:%ls [calling]\n",
2390 (unsigned)pName->Length / sizeof(WCHAR), pName->Buffer, rcNtResolve,
2391 pfFlags ? *pfFlags : UINT32_MAX, pwszSearchPath,
2392 !((uintptr_t)pwszSearchPath & 1) && (uintptr_t)pwszSearchPath >= 0x2000U ? pwszSearchPath : L"<flags>"));
2393 }
2394
2395 RtlRestoreLastWin32Error(dwSavedLastError);
2396 rcNt = g_pfnLdrLoadDllReal(pwszSearchPath, pfFlags, pName, phMod);
2397
2398 /*
2399 * Log the result and process pending WinVerifyTrust work if we can.
2400 */
2401 dwSavedLastError = RtlGetLastWin32Error();
2402
2403 if (NT_SUCCESS(rcNt) && phMod)
2404 SUP_DPRINTF(("supR3HardenedMonitor_LdrLoadDll: returns rcNt=%#x hMod=%p '%ls'\n", rcNt, *phMod, wszPath));
2405 else if (!NT_SUCCESS(rcNt) || !fQuiet)
2406 SUP_DPRINTF(("supR3HardenedMonitor_LdrLoadDll: returns rcNt=%#x '%ls'\n", rcNt, wszPath));
2407
2408 supR3HardenedWinVerifyCacheProcessWvtTodos();
2409
2410 RtlRestoreLastWin32Error(dwSavedLastError);
2411
2412 return rcNt;
2413}
2414
2415
2416/**
2417 * DLL load and unload notification callback.
2418 *
2419 * This is a safety against our LdrLoadDll hook being replaced by protection
2420 * software. Though, we prefer the LdrLoadDll hook to this one as it allows us
2421 * to call WinVerifyTrust more freely.
2422 *
2423 * @param ulReason The reason we're called, see
2424 * LDR_DLL_NOTIFICATION_REASON_XXX.
2425 * @param pData Reason specific data. (Format is currently the same for
2426 * both load and unload.)
2427 * @param pvUser User parameter (ignored).
2428 *
2429 * @remarks Vista and later.
2430 * @remarks The loader lock is held when we're called, at least on Windows 7.
2431 */
2432static VOID CALLBACK
2433supR3HardenedDllNotificationCallback(ULONG ulReason, PCLDR_DLL_NOTIFICATION_DATA pData, PVOID pvUser) RT_NOTHROW_DEF
2434{
2435 NOREF(pvUser);
2436
2437 /*
2438 * Screen the image on load. We will normally get a verification cache
2439 * hit here because of the LdrLoadDll and NtCreateSection hooks, so it
2440 * should be relatively cheap to recheck. In case our NtDll patches
2441 * got re
2442 *
2443 * This ASSUMES that we get informed after the fact as indicated by the
2444 * available documentation.
2445 */
2446 if (ulReason == LDR_DLL_NOTIFICATION_REASON_LOADED)
2447 {
2448 SUP_DPRINTF(("supR3HardenedDllNotificationCallback: load %p LB %#010x %.*ls [fFlags=%#x]\n",
2449 pData->Loaded.DllBase, pData->Loaded.SizeOfImage,
2450 pData->Loaded.FullDllName->Length / sizeof(WCHAR), pData->Loaded.FullDllName->Buffer,
2451 pData->Loaded.Flags));
2452
2453 /* Convert the windows path to an NT path and open it. */
2454 HANDLE hRootDir;
2455 UNICODE_STRING NtPathUniStr;
2456 int rc = RTNtPathFromWinUtf16Ex(&NtPathUniStr, &hRootDir, pData->Loaded.FullDllName->Buffer,
2457 pData->Loaded.FullDllName->Length / sizeof(WCHAR));
2458 if (RT_FAILURE(rc))
2459 {
2460 supR3HardenedFatal("supR3HardenedDllNotificationCallback: RTNtPathFromWinUtf16Ex failed on '%.*ls': %Rrc\n",
2461 pData->Loaded.FullDllName->Length / sizeof(WCHAR), pData->Loaded.FullDllName->Buffer, rc);
2462 return;
2463 }
2464
2465 HANDLE hFile = RTNT_INVALID_HANDLE_VALUE;
2466 IO_STATUS_BLOCK Ios = RTNT_IO_STATUS_BLOCK_INITIALIZER;
2467 OBJECT_ATTRIBUTES ObjAttr;
2468 InitializeObjectAttributes(&ObjAttr, &NtPathUniStr, OBJ_CASE_INSENSITIVE, hRootDir, NULL /*pSecDesc*/);
2469
2470 NTSTATUS rcNt = NtCreateFile(&hFile,
2471 FILE_READ_DATA | READ_CONTROL | SYNCHRONIZE,
2472 &ObjAttr,
2473 &Ios,
2474 NULL /* Allocation Size*/,
2475 FILE_ATTRIBUTE_NORMAL,
2476 FILE_SHARE_READ,
2477 FILE_OPEN,
2478 FILE_NON_DIRECTORY_FILE | FILE_SYNCHRONOUS_IO_NONALERT,
2479 NULL /*EaBuffer*/,
2480 0 /*EaLength*/);
2481 if (NT_SUCCESS(rcNt))
2482 rcNt = Ios.Status;
2483 if (!NT_SUCCESS(rcNt))
2484 {
2485 supR3HardenedFatal("supR3HardenedDllNotificationCallback: NtCreateFile failed on '%.*ls' / '%.*ls': %#x\n",
2486 pData->Loaded.FullDllName->Length / sizeof(WCHAR), pData->Loaded.FullDllName->Buffer,
2487 NtPathUniStr.Length / sizeof(WCHAR), NtPathUniStr.Buffer, rcNt);
2488 /* not reached */
2489 }
2490
2491 /* Do the screening. */
2492 ULONG fAccess = 0;
2493 ULONG fProtect = 0;
2494 bool fCallRealApi = false;
2495 bool fQuietFailure = false;
2496 rcNt = supR3HardenedScreenImage(hFile, true /*fImage*/, true /*fIgnoreArch*/, &fAccess, &fProtect, &fCallRealApi,
2497 "LdrLoadDll", true /*fAvoidWinVerifyTrust*/, &fQuietFailure);
2498 NtClose(hFile);
2499 if (!NT_SUCCESS(rcNt))
2500 {
2501 supR3HardenedFatal("supR3HardenedDllNotificationCallback: supR3HardenedScreenImage failed on '%.*ls' / '%.*ls': %#x\n",
2502 pData->Loaded.FullDllName->Length / sizeof(WCHAR), pData->Loaded.FullDllName->Buffer,
2503 NtPathUniStr.Length / sizeof(WCHAR), NtPathUniStr.Buffer, rcNt);
2504 /* not reached */
2505 }
2506 RTNtPathFree(&NtPathUniStr, &hRootDir);
2507 }
2508 /*
2509 * Log the unload call.
2510 */
2511 else if (ulReason == LDR_DLL_NOTIFICATION_REASON_UNLOADED)
2512 {
2513 SUP_DPRINTF(("supR3HardenedDllNotificationCallback: Unload %p LB %#010x %.*ls [flags=%#x]\n",
2514 pData->Unloaded.DllBase, pData->Unloaded.SizeOfImage,
2515 pData->Unloaded.FullDllName->Length / sizeof(WCHAR), pData->Unloaded.FullDllName->Buffer,
2516 pData->Unloaded.Flags));
2517 }
2518 /*
2519 * Just log things we don't know and then return without caching anything.
2520 */
2521 else
2522 {
2523 static uint32_t s_cLogEntries = 0;
2524 if (s_cLogEntries++ < 32)
2525 SUP_DPRINTF(("supR3HardenedDllNotificationCallback: ulReason=%u pData=%p\n", ulReason, pData));
2526 return;
2527 }
2528
2529 /*
2530 * Use this opportunity to make sure our NtDll patches are still in place,
2531 * since they may be replaced by indecent protection software solutions.
2532 */
2533 supR3HardenedWinReInstallHooks(false /*fFirstCall */);
2534}
2535
2536
2537/**
2538 * Registers the DLL notification callback if it hasn't already been registered.
2539 */
2540static void supR3HardenedWinRegisterDllNotificationCallback(void)
2541{
2542 /*
2543 * The notification API was added in Vista, so it's an optional (weak) import.
2544 */
2545 if ( LdrRegisterDllNotification != NULL
2546 && g_cDllNotificationRegistered <= 0
2547 && g_cDllNotificationRegistered > -32)
2548 {
2549 NTSTATUS rcNt = LdrRegisterDllNotification(0, supR3HardenedDllNotificationCallback, NULL, &g_pvDllNotificationCookie);
2550 if (NT_SUCCESS(rcNt))
2551 {
2552 SUP_DPRINTF(("Registered Dll notification callback with NTDLL.\n"));
2553 g_cDllNotificationRegistered = 1;
2554 }
2555 else
2556 {
2557 supR3HardenedError(rcNt, false /*fFatal*/, "LdrRegisterDllNotification failed: %#x\n", rcNt);
2558 g_cDllNotificationRegistered--;
2559 }
2560 }
2561}
2562
2563
2564/**
2565 * Dummy replacement routine we use for passifying unwanted user APC
2566 * callbacks during early process initialization.
2567 *
2568 * @sa supR3HardenedMonitor_KiUserApcDispatcher_C
2569 */
2570static VOID NTAPI supR3HardenedWinDummyApcRoutine(PVOID pvArg1, PVOID pvArg2, PVOID pvArg3)
2571{
2572 SUP_DPRINTF(("supR3HardenedWinDummyApcRoutine: pvArg1=%p pvArg2=%p pvArg3=%p\n", pvArg1, pvArg2, pvArg3));
2573 RT_NOREF(pvArg1, pvArg2, pvArg3);
2574}
2575
2576
2577/**
2578 * This is called when ntdll!KiUserApcDispatcher is invoked (via
2579 * supR3HardenedMonitor_KiUserApcDispatcher).
2580 *
2581 * The parent process hooks KiUserApcDispatcher before the guest starts
2582 * executing. There should only be one APC request dispatched while the process
2583 * is being initialized, and that's the one calling ntdll!LdrInitializeThunk.
2584 *
2585 * @returns Where to go to run the original code.
2586 * @param pvApcArgs The APC dispatcher arguments.
2587 */
2588DECLASM(uintptr_t) supR3HardenedMonitor_KiUserApcDispatcher_C(void *pvApcArgs)
2589{
2590#ifdef RT_ARCH_AMD64
2591 PCONTEXT pCtx = (PCONTEXT)pvApcArgs;
2592 uintptr_t *ppfnRoutine = (uintptr_t *)&pCtx->P4Home;
2593#else
2594 struct X86APCCTX
2595 {
2596 uintptr_t pfnRoutine;
2597 uintptr_t pvCtx;
2598 uintptr_t pvUser1;
2599 uintptr_t pvUser2;
2600 CONTEXT Ctx;
2601 } *pCtx = (struct X86APCCTX *)pvApcArgs;
2602 uintptr_t *ppfnRoutine = &pCtx->pfnRoutine;
2603#endif
2604 uintptr_t pfnRoutine = *ppfnRoutine;
2605
2606 if (g_enmSupR3HardenedMainState < SUPR3HARDENEDMAINSTATE_HARDENED_MAIN_CALLED)
2607 {
2608 if (pfnRoutine == g_pfnLdrInitializeThunk) /* Note! we could use this to detect thread creation too. */
2609 SUP_DPRINTF(("supR3HardenedMonitor_KiUserApcDispatcher_C: pfnRoutine=%p enmState=%d - okay\n",
2610 pfnRoutine, g_enmSupR3HardenedMainState));
2611 else
2612 {
2613 *ppfnRoutine = (uintptr_t)supR3HardenedWinDummyApcRoutine;
2614 SUP_DPRINTF(("supR3HardenedMonitor_KiUserApcDispatcher_C: pfnRoutine=%p enmState=%d -> supR3HardenedWinDummyApcRoutine\n",
2615 pfnRoutine, g_enmSupR3HardenedMainState));
2616 }
2617 }
2618 return (uintptr_t)g_pfnKiUserApcDispatcherReal;
2619}
2620
2621
2622/**
2623 * SUP_DPRINTF on pCtx, with lead-in text.
2624 */
2625static void supR3HardNtDprintCtx(PCONTEXT pCtx, const char *pszLeadIn)
2626{
2627#ifdef RT_ARCH_AMD64
2628 SUP_DPRINTF(("%s\n"
2629 " rax=%016RX64 rbx=%016RX64 rcx=%016RX64 rdx=%016RX64\n"
2630 " rsi=%016RX64 rdi=%016RX64 r8 =%016RX64 r9 =%016RX64\n"
2631 " r10=%016RX64 r11=%016RX64 r12=%016RX64 r13=%016RX64\n"
2632 " r14=%016RX64 r15=%016RX64 P1=%016RX64 P2=%016RX64\n"
2633 " rip=%016RX64 rsp=%016RX64 rbp=%016RX64 ctxflags=%08x\n"
2634 " cs=%04x ss=%04x ds=%04x es=%04x fs=%04x gs=%04x eflags=%08x mxcrx=%08x\n"
2635 " P3=%016RX64 P4=%016RX64 P5=%016RX64 P6=%016RX64\n"
2636 " dr0=%016RX64 dr1=%016RX64 dr2=%016RX64 dr3=%016RX64\n"
2637 " dr6=%016RX64 dr7=%016RX64 vcr=%016RX64 dcr=%016RX64\n"
2638 " lbt=%016RX64 lbf=%016RX64 lxt=%016RX64 lxf=%016RX64\n"
2639 ,
2640 pszLeadIn,
2641 pCtx->Rax, pCtx->Rbx, pCtx->Rcx, pCtx->Rdx,
2642 pCtx->Rsi, pCtx->Rdi, pCtx->R8, pCtx->R9,
2643 pCtx->R10, pCtx->R11, pCtx->R12, pCtx->R13,
2644 pCtx->R14, pCtx->R15, pCtx->P1Home, pCtx->P2Home,
2645 pCtx->Rip, pCtx->Rsp, pCtx->Rbp, pCtx->ContextFlags,
2646 pCtx->SegCs, pCtx->SegSs, pCtx->SegDs, pCtx->SegEs, pCtx->SegFs, pCtx->SegGs, pCtx->EFlags, pCtx->MxCsr,
2647 pCtx->P3Home, pCtx->P4Home, pCtx->P5Home, pCtx->P6Home,
2648 pCtx->Dr0, pCtx->Dr1, pCtx->Dr2, pCtx->Dr3,
2649 pCtx->Dr6, pCtx->Dr7, pCtx->VectorControl, pCtx->DebugControl,
2650 pCtx->LastBranchToRip, pCtx->LastBranchFromRip, pCtx->LastExceptionToRip, pCtx->LastExceptionFromRip ));
2651#elif defined(RT_ARCH_X86)
2652 SUP_DPRINTF(("%s\n"
2653 " eax=%08RX32 ebx=%08RX32 ecx=%08RX32 edx=%08RX32 esi=%08rx64 edi=%08RX32\n"
2654 " eip=%08RX32 esp=%08RX32 ebp=%08RX32 eflags=%08RX32\n"
2655 " cs=%04RX16 ds=%04RX16 es=%04RX16 fs=%04RX16 gs=%04RX16\n"
2656 " dr0=%08RX32 dr1=%08RX32 dr2=%08RX32 dr3=%08RX32 dr6=%08RX32 dr7=%08RX32\n",
2657 pszLeadIn,
2658 pCtx->Eax, pCtx->Ebx, pCtx->Ecx, pCtx->Edx, pCtx->Esi, pCtx->Edi,
2659 pCtx->Eip, pCtx->Esp, pCtx->Ebp, pCtx->EFlags,
2660 pCtx->SegCs, pCtx->SegDs, pCtx->SegEs, pCtx->SegFs, pCtx->SegGs,
2661 pCtx->Dr0, pCtx->Dr1, pCtx->Dr2, pCtx->Dr3, pCtx->Dr6, pCtx->Dr7));
2662#else
2663# error "Unsupported arch."
2664#endif
2665 RT_NOREF(pCtx, pszLeadIn);
2666}
2667
2668
2669#ifndef VBOX_WITHOUT_HARDENDED_XCPT_LOGGING
2670/**
2671 * This is called when ntdll!KiUserExceptionDispatcher is invoked (via
2672 * supR3HardenedMonitor_KiUserExceptionDispatcher).
2673 *
2674 * For 64-bit processes there is a return and two parameters on the stack.
2675 *
2676 * @returns Where to go to run the original code.
2677 * @param pXcptRec The exception record.
2678 * @param pCtx The exception context.
2679 */
2680DECLASM(uintptr_t) supR3HardenedMonitor_KiUserExceptionDispatcher_C(PEXCEPTION_RECORD pXcptRec, PCONTEXT pCtx)
2681{
2682 /*
2683 * Ignore the guard page violation.
2684 */
2685 if (pXcptRec->ExceptionCode == STATUS_GUARD_PAGE_VIOLATION)
2686 return (uintptr_t)g_pfnKiUserExceptionDispatcherReal;
2687
2688 /*
2689 * Log the exception and context.
2690 */
2691 char szLeadIn[384];
2692 if (pXcptRec->NumberParameters == 0)
2693 RTStrPrintf(szLeadIn, sizeof(szLeadIn), "KiUserExceptionDispatcher: %#x @ %p (flags=%#x)",
2694 pXcptRec->ExceptionCode, pXcptRec->ExceptionAddress, pXcptRec->ExceptionFlags);
2695 else if (pXcptRec->NumberParameters == 1)
2696 RTStrPrintf(szLeadIn, sizeof(szLeadIn), "KiUserExceptionDispatcher: %#x (%p) @ %p (flags=%#x)",
2697 pXcptRec->ExceptionCode, pXcptRec->ExceptionInformation[0],
2698 pXcptRec->ExceptionAddress, pXcptRec->ExceptionFlags);
2699 else if (pXcptRec->NumberParameters == 2)
2700 RTStrPrintf(szLeadIn, sizeof(szLeadIn), "KiUserExceptionDispatcher: %#x (%p, %p) @ %p (flags=%#x)",
2701 pXcptRec->ExceptionCode, pXcptRec->ExceptionInformation[0], pXcptRec->ExceptionInformation[1],
2702 pXcptRec->ExceptionAddress, pXcptRec->ExceptionFlags);
2703 else if (pXcptRec->NumberParameters == 3)
2704 RTStrPrintf(szLeadIn, sizeof(szLeadIn), "KiUserExceptionDispatcher: %#x (%p, %p, %p) @ %p (flags=%#x)",
2705 pXcptRec->ExceptionCode, pXcptRec->ExceptionInformation[0], pXcptRec->ExceptionInformation[1],
2706 pXcptRec->ExceptionInformation[2], pXcptRec->ExceptionAddress, pXcptRec->ExceptionFlags);
2707 else
2708 RTStrPrintf(szLeadIn, sizeof(szLeadIn), "KiUserExceptionDispatcher: %#x (#%u: %p, %p, %p, %p, %p, %p, %p, %p, ...) @ %p (flags=%#x)",
2709 pXcptRec->ExceptionCode, pXcptRec->NumberParameters,
2710 pXcptRec->ExceptionInformation[0], pXcptRec->ExceptionInformation[1],
2711 pXcptRec->ExceptionInformation[2], pXcptRec->ExceptionInformation[3],
2712 pXcptRec->ExceptionInformation[4], pXcptRec->ExceptionInformation[5],
2713 pXcptRec->ExceptionInformation[6], pXcptRec->ExceptionInformation[7],
2714 pXcptRec->ExceptionAddress, pXcptRec->ExceptionFlags);
2715 supR3HardNtDprintCtx(pCtx, szLeadIn);
2716
2717 return (uintptr_t)g_pfnKiUserExceptionDispatcherReal;
2718}
2719#endif /* !VBOX_WITHOUT_HARDENDED_XCPT_LOGGING */
2720
2721
2722static void supR3HardenedWinHookFailed(const char *pszWhich, uint8_t const *pbPrologue)
2723{
2724 supR3HardenedFatalMsg("supR3HardenedWinInstallHooks", kSupInitOp_Misc, VERR_NO_MEMORY,
2725 "Failed to install %s monitor: %x %x %x %x %x %x %x %x %x %x %x %x %x %x %x %x\n "
2726#ifdef RT_ARCH_X86
2727 "(It is also possible you are running 32-bit VirtualBox under 64-bit windows.)\n"
2728#endif
2729 ,
2730 pszWhich,
2731 pbPrologue[0], pbPrologue[1], pbPrologue[2], pbPrologue[3],
2732 pbPrologue[4], pbPrologue[5], pbPrologue[6], pbPrologue[7],
2733 pbPrologue[8], pbPrologue[9], pbPrologue[10], pbPrologue[11],
2734 pbPrologue[12], pbPrologue[13], pbPrologue[14], pbPrologue[15]);
2735}
2736
2737
2738/**
2739 * IPRT thread that waits for the parent process to terminate and reacts by
2740 * exiting the current process.
2741 *
2742 * @returns VINF_SUCCESS
2743 * @param hSelf The current thread. Ignored.
2744 * @param pvUser The handle of the parent process.
2745 */
2746static DECLCALLBACK(int) supR3HardenedWinParentWatcherThread(RTTHREAD hSelf, void *pvUser)
2747{
2748 HANDLE hProcWait = (HANDLE)pvUser;
2749 NOREF(hSelf);
2750
2751 /*
2752 * Wait for the parent to terminate.
2753 */
2754 NTSTATUS rcNt;
2755 for (;;)
2756 {
2757 rcNt = NtWaitForSingleObject(hProcWait, TRUE /*Alertable*/, NULL /*pTimeout*/);
2758 if ( rcNt == STATUS_WAIT_0
2759 || rcNt == STATUS_ABANDONED_WAIT_0)
2760 break;
2761 if ( rcNt != STATUS_TIMEOUT
2762 && rcNt != STATUS_USER_APC
2763 && rcNt != STATUS_ALERTED)
2764 supR3HardenedFatal("NtWaitForSingleObject returned %#x\n", rcNt);
2765 }
2766
2767 /*
2768 * Proxy the termination code of the child, if it exited already.
2769 */
2770 PROCESS_BASIC_INFORMATION BasicInfo;
2771 NTSTATUS rcNt2 = NtQueryInformationProcess(hProcWait, ProcessBasicInformation, &BasicInfo, sizeof(BasicInfo), NULL);
2772 if ( !NT_SUCCESS(rcNt2)
2773 || BasicInfo.ExitStatus == STATUS_PENDING)
2774 BasicInfo.ExitStatus = RTEXITCODE_FAILURE;
2775
2776 NtClose(hProcWait);
2777 SUP_DPRINTF(("supR3HardenedWinParentWatcherThread: Quitting: ExitCode=%#x rcNt=%#x\n", BasicInfo.ExitStatus, rcNt));
2778 suplibHardenedExit((RTEXITCODE)BasicInfo.ExitStatus);
2779 /* not reached */
2780}
2781
2782
2783/**
2784 * Creates the parent watcher thread that will make sure this process exits when
2785 * the parent does.
2786 *
2787 * This is a necessary evil to make VBoxNetDhcp and VBoxNetNat termination from
2788 * Main work without too much new magic. It also makes Ctrl-C or similar work
2789 * in on the hardened processes in the windows console.
2790 *
2791 * @param hVBoxRT The VBoxRT.dll handle. We use RTThreadCreate to
2792 * spawn the thread to avoid duplicating thread
2793 * creation and thread naming code from IPRT.
2794 */
2795DECLHIDDEN(void) supR3HardenedWinCreateParentWatcherThread(HMODULE hVBoxRT)
2796{
2797 /*
2798 * Resolve runtime methods that we need.
2799 */
2800 PFNRTTHREADCREATE pfnRTThreadCreate = (PFNRTTHREADCREATE)GetProcAddress(hVBoxRT, "RTThreadCreate");
2801 SUPR3HARDENED_ASSERT(pfnRTThreadCreate != NULL);
2802
2803 /*
2804 * Find the parent process ID.
2805 */
2806 PROCESS_BASIC_INFORMATION BasicInfo;
2807 NTSTATUS rcNt = NtQueryInformationProcess(NtCurrentProcess(), ProcessBasicInformation, &BasicInfo, sizeof(BasicInfo), NULL);
2808 if (!NT_SUCCESS(rcNt))
2809 supR3HardenedFatal("supR3HardenedWinCreateParentWatcherThread: NtQueryInformationProcess failed: %#x\n", rcNt);
2810
2811 /*
2812 * Open the parent process for waiting and exitcode query.
2813 */
2814 OBJECT_ATTRIBUTES ObjAttr;
2815 InitializeObjectAttributes(&ObjAttr, NULL, 0, NULL /*hRootDir*/, NULL /*pSecDesc*/);
2816
2817 CLIENT_ID ClientId;
2818 ClientId.UniqueProcess = (HANDLE)BasicInfo.InheritedFromUniqueProcessId;
2819 ClientId.UniqueThread = NULL;
2820
2821 HANDLE hParent;
2822 rcNt = NtOpenProcess(&hParent, SYNCHRONIZE | PROCESS_QUERY_INFORMATION, &ObjAttr, &ClientId);
2823 if (!NT_SUCCESS(rcNt))
2824 supR3HardenedFatalMsg("supR3HardenedWinCreateParentWatcherThread", kSupInitOp_Misc, VERR_GENERAL_FAILURE,
2825 "NtOpenProcess(%p.0) failed: %#x\n", ClientId.UniqueProcess, rcNt);
2826
2827 /*
2828 * Create the thread that should do the waiting.
2829 */
2830 int rc = pfnRTThreadCreate(NULL, supR3HardenedWinParentWatcherThread, hParent, _64K /* stack */,
2831 RTTHREADTYPE_DEFAULT, 0 /*fFlags*/, "ParentWatcher");
2832 if (RT_FAILURE(rc))
2833 supR3HardenedFatal("supR3HardenedWinCreateParentWatcherThread: RTThreadCreate failed: %Rrc\n", rc);
2834}
2835
2836
2837/**
2838 * Checks if the calling thread is the only one in the process.
2839 *
2840 * @returns true if we're positive we're alone, false if not.
2841 */
2842static bool supR3HardenedWinAmIAlone(void) RT_NOTHROW_DEF
2843{
2844 ULONG fAmIAlone = 0;
2845 ULONG cbIgn = 0;
2846 NTSTATUS rcNt = NtQueryInformationThread(NtCurrentThread(), ThreadAmILastThread, &fAmIAlone, sizeof(fAmIAlone), &cbIgn);
2847 Assert(NT_SUCCESS(rcNt));
2848 return NT_SUCCESS(rcNt) && fAmIAlone != 0;
2849}
2850
2851
2852/**
2853 * Simplify NtProtectVirtualMemory interface.
2854 *
2855 * Modifies protection for the current process. Caller must know the current
2856 * protection as it's not returned.
2857 *
2858 * @returns NT status code.
2859 * @param pvMem The memory to change protection for.
2860 * @param cbMem The amount of memory to change.
2861 * @param fNewProt The new protection.
2862 */
2863static NTSTATUS supR3HardenedWinProtectMemory(PVOID pvMem, SIZE_T cbMem, ULONG fNewProt) RT_NOTHROW_DEF
2864{
2865 ULONG fOldProt = 0;
2866 return NtProtectVirtualMemory(NtCurrentProcess(), &pvMem, &cbMem, fNewProt, &fOldProt);
2867}
2868
2869
2870/**
2871 * Installs or reinstalls the NTDLL patches.
2872 */
2873static void supR3HardenedWinReInstallHooks(bool fFirstCall) RT_NOTHROW_DEF
2874{
2875 struct
2876 {
2877 size_t cbPatch;
2878 uint8_t const *pabPatch;
2879 uint8_t **ppbApi;
2880 const char *pszName;
2881 } const s_aPatches[] =
2882 {
2883 { sizeof(g_abNtCreateSectionPatch), g_abNtCreateSectionPatch, &g_pbNtCreateSection, "NtCreateSection" },
2884 { sizeof(g_abLdrLoadDllPatch), g_abLdrLoadDllPatch, &g_pbLdrLoadDll, "LdrLoadDll" },
2885 { sizeof(g_abKiUserApcDispatcherPatch), g_abKiUserApcDispatcherPatch, &g_pbKiUserApcDispatcher, "KiUserApcDispatcher" },
2886#ifndef VBOX_WITHOUT_HARDENDED_XCPT_LOGGING
2887 { sizeof(g_abKiUserExceptionDispatcherPatch), g_abKiUserExceptionDispatcherPatch, &g_pbKiUserExceptionDispatcher, "KiUserExceptionDispatcher" },
2888#endif
2889 };
2890
2891 ULONG fAmIAlone = ~(ULONG)0;
2892
2893 for (uint32_t i = 0; i < RT_ELEMENTS(s_aPatches); i++)
2894 {
2895 uint8_t *pbApi = *s_aPatches[i].ppbApi;
2896 if (memcmp(pbApi, s_aPatches[i].pabPatch, s_aPatches[i].cbPatch) != 0)
2897 {
2898 /*
2899 * Log the incident if it's not the initial call.
2900 */
2901 static uint32_t volatile s_cTimes = 0;
2902 if (!fFirstCall && s_cTimes < 128)
2903 {
2904 s_cTimes++;
2905 SUP_DPRINTF(("supR3HardenedWinReInstallHooks: Reinstalling %s (%p: %.*Rhxs).\n",
2906 s_aPatches[i].pszName, pbApi, s_aPatches[i].cbPatch, pbApi));
2907 }
2908
2909 Assert(s_aPatches[i].cbPatch >= 4);
2910
2911 SUPR3HARDENED_ASSERT_NT_SUCCESS(supR3HardenedWinProtectMemory(pbApi, s_aPatches[i].cbPatch, PAGE_EXECUTE_READWRITE));
2912
2913 /*
2914 * If we're alone, just memcpy the patch in.
2915 */
2916
2917 if (fAmIAlone == ~(ULONG)0)
2918 fAmIAlone = supR3HardenedWinAmIAlone();
2919 if (fAmIAlone)
2920 memcpy(pbApi, s_aPatches[i].pabPatch, s_aPatches[i].cbPatch);
2921 else
2922 {
2923 /*
2924 * Not alone. Start by injecting a JMP $-2, then waste some
2925 * CPU cycles to get the other threads a good chance of getting
2926 * out of the code before we replace it.
2927 */
2928 RTUINT32U uJmpDollarMinus;
2929 uJmpDollarMinus.au8[0] = 0xeb;
2930 uJmpDollarMinus.au8[1] = 0xfe;
2931 uJmpDollarMinus.au8[2] = pbApi[2];
2932 uJmpDollarMinus.au8[3] = pbApi[3];
2933 ASMAtomicXchgU32((uint32_t volatile *)pbApi, uJmpDollarMinus.u);
2934
2935 NtYieldExecution();
2936 NtYieldExecution();
2937
2938 /* Copy in the tail bytes of the patch, then xchg the jmp $-2. */
2939 if (s_aPatches[i].cbPatch > 4)
2940 memcpy(&pbApi[4], &s_aPatches[i].pabPatch[4], s_aPatches[i].cbPatch - 4);
2941 ASMAtomicXchgU32((uint32_t volatile *)pbApi, *(uint32_t *)s_aPatches[i].pabPatch);
2942 }
2943
2944 SUPR3HARDENED_ASSERT_NT_SUCCESS(supR3HardenedWinProtectMemory(pbApi, s_aPatches[i].cbPatch, PAGE_EXECUTE_READ));
2945 }
2946 }
2947}
2948
2949
2950/**
2951 * Install hooks for intercepting calls dealing with mapping shared libraries
2952 * into the process.
2953 *
2954 * This allows us to prevent undesirable shared libraries from being loaded.
2955 *
2956 * @remarks We assume we're alone in this process, so no seralizing trickery is
2957 * necessary when installing the patch.
2958 *
2959 * @remarks We would normally just copy the prologue sequence somewhere and add
2960 * a jump back at the end of it. But because we wish to avoid
2961 * allocating executable memory, we need to have preprepared assembly
2962 * "copies". This makes the non-system call patching a little tedious
2963 * and inflexible.
2964 */
2965static void supR3HardenedWinInstallHooks(void)
2966{
2967 NTSTATUS rcNt;
2968
2969 /*
2970 * Disable hard error popups so we can quietly refuse images to be loaded.
2971 */
2972 ULONG fHardErr = 0;
2973 rcNt = NtQueryInformationProcess(NtCurrentProcess(), ProcessDefaultHardErrorMode, &fHardErr, sizeof(fHardErr), NULL);
2974 if (!NT_SUCCESS(rcNt))
2975 supR3HardenedFatalMsg("supR3HardenedWinInstallHooks", kSupInitOp_Misc, VERR_GENERAL_FAILURE,
2976 "NtQueryInformationProcess/ProcessDefaultHardErrorMode failed: %#x\n", rcNt);
2977 if (fHardErr & PROCESS_HARDERR_CRITICAL_ERROR)
2978 {
2979 fHardErr &= ~PROCESS_HARDERR_CRITICAL_ERROR;
2980 rcNt = NtSetInformationProcess(NtCurrentProcess(), ProcessDefaultHardErrorMode, &fHardErr, sizeof(fHardErr));
2981 if (!NT_SUCCESS(rcNt))
2982 supR3HardenedFatalMsg("supR3HardenedWinInstallHooks", kSupInitOp_Misc, VERR_GENERAL_FAILURE,
2983 "NtSetInformationProcess/ProcessDefaultHardErrorMode failed: %#x\n", rcNt);
2984 }
2985
2986 /*
2987 * Locate the routines first so we can allocate memory that's near enough.
2988 */
2989 PFNRT pfnNtCreateSection = supR3HardenedWinGetRealDllSymbol("ntdll.dll", "NtCreateSection");
2990 SUPR3HARDENED_ASSERT(pfnNtCreateSection != NULL);
2991 //SUPR3HARDENED_ASSERT(pfnNtCreateSection == (FARPROC)NtCreateSection);
2992
2993 PFNRT pfnLdrLoadDll = supR3HardenedWinGetRealDllSymbol("ntdll.dll", "LdrLoadDll");
2994 SUPR3HARDENED_ASSERT(pfnLdrLoadDll != NULL);
2995 //SUPR3HARDENED_ASSERT(pfnLdrLoadDll == (FARPROC)LdrLoadDll);
2996
2997 PFNRT pfnKiUserApcDispatcher = supR3HardenedWinGetRealDllSymbol("ntdll.dll", "KiUserApcDispatcher");
2998 SUPR3HARDENED_ASSERT(pfnKiUserApcDispatcher != NULL);
2999 g_pfnLdrInitializeThunk = (uintptr_t)supR3HardenedWinGetRealDllSymbol("ntdll.dll", "LdrInitializeThunk");
3000 SUPR3HARDENED_ASSERT(g_pfnLdrInitializeThunk != NULL);
3001
3002#ifndef VBOX_WITHOUT_HARDENDED_XCPT_LOGGING
3003 PFNRT pfnKiUserExceptionDispatcher = supR3HardenedWinGetRealDllSymbol("ntdll.dll", "KiUserExceptionDispatcher");
3004 SUPR3HARDENED_ASSERT(pfnKiUserExceptionDispatcher != NULL);
3005#endif
3006
3007 /*
3008 * Exec page setup & management.
3009 */
3010 uint32_t offExecPage = 0;
3011 memset(g_abSupHardReadWriteExecPage, 0xcc, PAGE_SIZE);
3012
3013 /*
3014 * Hook #1 - NtCreateSection.
3015 * Purpose: Validate everything that can be mapped into the process before
3016 * it's mapped and we still have a file handle to work with.
3017 */
3018 uint8_t * const pbNtCreateSection = (uint8_t *)(uintptr_t)pfnNtCreateSection;
3019 g_pbNtCreateSection = pbNtCreateSection;
3020 memcpy(g_abNtCreateSectionPatch, pbNtCreateSection, sizeof(g_abNtCreateSectionPatch));
3021
3022 g_pfnNtCreateSectionReal = NtCreateSection; /* our direct syscall */
3023
3024#ifdef RT_ARCH_AMD64
3025 /*
3026 * Patch 64-bit hosts.
3027 */
3028 /* Pattern #1: XP64/W2K3-64 thru Windows 8.1
3029 0:000> u ntdll!NtCreateSection
3030 ntdll!NtCreateSection:
3031 00000000`779f1750 4c8bd1 mov r10,rcx
3032 00000000`779f1753 b847000000 mov eax,47h
3033 00000000`779f1758 0f05 syscall
3034 00000000`779f175a c3 ret
3035 00000000`779f175b 0f1f440000 nop dword ptr [rax+rax]
3036 The variant is the value loaded into eax: W2K3=??, Vista=47h?, W7=47h, W80=48h, W81=49h */
3037
3038 /* Assemble the patch. */
3039 g_abNtCreateSectionPatch[0] = 0x48; /* mov rax, qword */
3040 g_abNtCreateSectionPatch[1] = 0xb8;
3041 *(uint64_t *)&g_abNtCreateSectionPatch[2] = (uint64_t)supR3HardenedMonitor_NtCreateSection;
3042 g_abNtCreateSectionPatch[10] = 0xff; /* jmp rax */
3043 g_abNtCreateSectionPatch[11] = 0xe0;
3044
3045#else
3046 /*
3047 * Patch 32-bit hosts.
3048 */
3049 /* Pattern #1: XP thru Windows 7
3050 kd> u ntdll!NtCreateSection
3051 ntdll!NtCreateSection:
3052 7c90d160 b832000000 mov eax,32h
3053 7c90d165 ba0003fe7f mov edx,offset SharedUserData!SystemCallStub (7ffe0300)
3054 7c90d16a ff12 call dword ptr [edx]
3055 7c90d16c c21c00 ret 1Ch
3056 7c90d16f 90 nop
3057 The variable bit is the value loaded into eax: XP=32h, W2K3=34h, Vista=4bh, W7=54h
3058
3059 Pattern #2: Windows 8.1
3060 0:000:x86> u ntdll_6a0f0000!NtCreateSection
3061 ntdll_6a0f0000!NtCreateSection:
3062 6a15eabc b854010000 mov eax,154h
3063 6a15eac1 e803000000 call ntdll_6a0f0000!NtCreateSection+0xd (6a15eac9)
3064 6a15eac6 c21c00 ret 1Ch
3065 6a15eac9 8bd4 mov edx,esp
3066 6a15eacb 0f34 sysenter
3067 6a15eacd c3 ret
3068 The variable bit is the value loaded into eax: W81=154h */
3069
3070 /* Assemble the patch. */
3071 g_abNtCreateSectionPatch[0] = 0xe9; /* jmp rel32 */
3072 *(uint32_t *)&g_abNtCreateSectionPatch[1] = (uintptr_t)supR3HardenedMonitor_NtCreateSection
3073 - (uintptr_t)&pbNtCreateSection[1+4];
3074
3075#endif
3076
3077 /*
3078 * Hook #2 - LdrLoadDll
3079 * Purpose: (a) Enforce LdrLoadDll search path constraints, and (b) pre-validate
3080 * DLLs so we can avoid calling WinVerifyTrust from the first hook,
3081 * and thus avoiding messing up the loader data on some installations.
3082 *
3083 * This differs from the above function in that is no a system call and
3084 * we're at the mercy of the compiler.
3085 */
3086 uint8_t * const pbLdrLoadDll = (uint8_t *)(uintptr_t)pfnLdrLoadDll;
3087 g_pbLdrLoadDll = pbLdrLoadDll;
3088 memcpy(g_abLdrLoadDllPatch, pbLdrLoadDll, sizeof(g_abLdrLoadDllPatch));
3089
3090 DISSTATE Dis;
3091 uint32_t cbInstr;
3092 uint32_t offJmpBack = 0;
3093
3094#ifdef RT_ARCH_AMD64
3095 /*
3096 * Patch 64-bit hosts.
3097 */
3098 /* Just use the disassembler to skip 12 bytes or more. */
3099 while (offJmpBack < 12)
3100 {
3101 cbInstr = 1;
3102 int rc = DISInstr(pbLdrLoadDll + offJmpBack, DISCPUMODE_64BIT, &Dis, &cbInstr);
3103 if ( RT_FAILURE(rc)
3104 || (Dis.pCurInstr->fOpType & (DISOPTYPE_CONTROLFLOW))
3105 || (Dis.x86.ModRM.Bits.Mod == 0 && Dis.x86.ModRM.Bits.Rm == 5 /* wrt RIP */) )
3106 supR3HardenedWinHookFailed("LdrLoadDll", pbLdrLoadDll);
3107 offJmpBack += cbInstr;
3108 }
3109
3110 /* Assemble the code for resuming the call.*/
3111 *(PFNRT *)&g_pfnLdrLoadDllReal = (PFNRT)(uintptr_t)&g_abSupHardReadWriteExecPage[offExecPage];
3112
3113 memcpy(&g_abSupHardReadWriteExecPage[offExecPage], pbLdrLoadDll, offJmpBack);
3114 offExecPage += offJmpBack;
3115
3116 g_abSupHardReadWriteExecPage[offExecPage++] = 0xff; /* jmp qword [$+8 wrt RIP] */
3117 g_abSupHardReadWriteExecPage[offExecPage++] = 0x25;
3118 *(uint32_t *)&g_abSupHardReadWriteExecPage[offExecPage] = RT_ALIGN_32(offExecPage + 4, 8) - (offExecPage + 4);
3119 offExecPage = RT_ALIGN_32(offExecPage + 4, 8);
3120 *(uint64_t *)&g_abSupHardReadWriteExecPage[offExecPage] = (uintptr_t)&pbLdrLoadDll[offJmpBack];
3121 offExecPage = RT_ALIGN_32(offExecPage + 8, 16);
3122
3123 /* Assemble the LdrLoadDll patch. */
3124 Assert(offJmpBack >= 12);
3125 g_abLdrLoadDllPatch[0] = 0x48; /* mov rax, qword */
3126 g_abLdrLoadDllPatch[1] = 0xb8;
3127 *(uint64_t *)&g_abLdrLoadDllPatch[2] = (uint64_t)supR3HardenedMonitor_LdrLoadDll;
3128 g_abLdrLoadDllPatch[10] = 0xff; /* jmp rax */
3129 g_abLdrLoadDllPatch[11] = 0xe0;
3130
3131#else
3132 /*
3133 * Patch 32-bit hosts.
3134 */
3135 /* Just use the disassembler to skip 5 bytes or more. */
3136 while (offJmpBack < 5)
3137 {
3138 cbInstr = 1;
3139 int rc = DISInstr(pbLdrLoadDll + offJmpBack, DISCPUMODE_32BIT, &Dis, &cbInstr);
3140 if ( RT_FAILURE(rc)
3141 || (Dis.pCurInstr->fOpType & (DISOPTYPE_CONTROLFLOW)) )
3142 supR3HardenedWinHookFailed("LdrLoadDll", pbLdrLoadDll);
3143 offJmpBack += cbInstr;
3144 }
3145
3146 /* Assemble the code for resuming the call.*/
3147 *(PFNRT *)&g_pfnLdrLoadDllReal = (PFNRT)(uintptr_t)&g_abSupHardReadWriteExecPage[offExecPage];
3148
3149 memcpy(&g_abSupHardReadWriteExecPage[offExecPage], pbLdrLoadDll, offJmpBack);
3150 offExecPage += offJmpBack;
3151
3152 g_abSupHardReadWriteExecPage[offExecPage++] = 0xe9; /* jmp rel32 */
3153 *(uint32_t *)&g_abSupHardReadWriteExecPage[offExecPage] = (uintptr_t)&pbLdrLoadDll[offJmpBack]
3154 - (uintptr_t)&g_abSupHardReadWriteExecPage[offExecPage + 4];
3155 offExecPage = RT_ALIGN_32(offExecPage + 4, 16);
3156
3157 /* Assemble the LdrLoadDll patch. */
3158 memcpy(g_abLdrLoadDllPatch, pbLdrLoadDll, sizeof(g_abLdrLoadDllPatch));
3159 Assert(offJmpBack >= 5);
3160 g_abLdrLoadDllPatch[0] = 0xe9;
3161 *(uint32_t *)&g_abLdrLoadDllPatch[1] = (uintptr_t)supR3HardenedMonitor_LdrLoadDll - (uintptr_t)&pbLdrLoadDll[1+4];
3162#endif
3163
3164 /*
3165 * Hook #3 - KiUserApcDispatcher
3166 * Purpose: Prevent user APC to memory we (or our parent) has freed from
3167 * crashing the process. Also ensures no code injection via user
3168 * APC during process init given the way we're vetting the APCs.
3169 *
3170 * This differs from the first function in that is no a system call and
3171 * we're at the mercy of the handwritten assembly.
3172 *
3173 * Note! We depend on all waits up past the patching to be non-altertable,
3174 * otherwise an APC might slip by us.
3175 */
3176 uint8_t * const pbKiUserApcDispatcher = (uint8_t *)(uintptr_t)pfnKiUserApcDispatcher;
3177 g_pbKiUserApcDispatcher = pbKiUserApcDispatcher;
3178 memcpy(g_abKiUserApcDispatcherPatch, pbKiUserApcDispatcher, sizeof(g_abKiUserApcDispatcherPatch));
3179
3180#ifdef RT_ARCH_AMD64
3181 /*
3182 * Patch 64-bit hosts.
3183 */
3184 /* Just use the disassembler to skip 12 bytes or more. */
3185 offJmpBack = 0;
3186 while (offJmpBack < 12)
3187 {
3188 cbInstr = 1;
3189 int rc = DISInstr(pbKiUserApcDispatcher + offJmpBack, DISCPUMODE_64BIT, &Dis, &cbInstr);
3190 if ( RT_FAILURE(rc)
3191 || (Dis.pCurInstr->fOpType & (DISOPTYPE_CONTROLFLOW))
3192 || (Dis.x86.ModRM.Bits.Mod == 0 && Dis.x86.ModRM.Bits.Rm == 5 /* wrt RIP */) )
3193 supR3HardenedWinHookFailed("KiUserApcDispatcher", pbKiUserApcDispatcher);
3194 offJmpBack += cbInstr;
3195 }
3196
3197 /* Assemble the code for resuming the call.*/
3198 *(PFNRT *)&g_pfnKiUserApcDispatcherReal = (PFNRT)(uintptr_t)&g_abSupHardReadWriteExecPage[offExecPage];
3199
3200 memcpy(&g_abSupHardReadWriteExecPage[offExecPage], pbKiUserApcDispatcher, offJmpBack);
3201 offExecPage += offJmpBack;
3202
3203 g_abSupHardReadWriteExecPage[offExecPage++] = 0xff; /* jmp qword [$+8 wrt RIP] */
3204 g_abSupHardReadWriteExecPage[offExecPage++] = 0x25;
3205 *(uint32_t *)&g_abSupHardReadWriteExecPage[offExecPage] = RT_ALIGN_32(offExecPage + 4, 8) - (offExecPage + 4);
3206 offExecPage = RT_ALIGN_32(offExecPage + 4, 8);
3207 *(uint64_t *)&g_abSupHardReadWriteExecPage[offExecPage] = (uintptr_t)&pbKiUserApcDispatcher[offJmpBack];
3208 offExecPage = RT_ALIGN_32(offExecPage + 8, 16);
3209
3210 /* Assemble the KiUserApcDispatcher patch. */
3211 Assert(offJmpBack >= 12);
3212 g_abKiUserApcDispatcherPatch[0] = 0x48; /* mov rax, qword */
3213 g_abKiUserApcDispatcherPatch[1] = 0xb8;
3214 *(uint64_t *)&g_abKiUserApcDispatcherPatch[2] = (uint64_t)supR3HardenedMonitor_KiUserApcDispatcher;
3215 g_abKiUserApcDispatcherPatch[10] = 0xff; /* jmp rax */
3216 g_abKiUserApcDispatcherPatch[11] = 0xe0;
3217
3218#else
3219 /*
3220 * Patch 32-bit hosts.
3221 */
3222 /* Just use the disassembler to skip 5 bytes or more. */
3223 offJmpBack = 0;
3224 while (offJmpBack < 5)
3225 {
3226 cbInstr = 1;
3227 int rc = DISInstr(pbKiUserApcDispatcher + offJmpBack, DISCPUMODE_32BIT, &Dis, &cbInstr);
3228 if ( RT_FAILURE(rc)
3229 || (Dis.pCurInstr->fOpType & (DISOPTYPE_CONTROLFLOW)) )
3230 supR3HardenedWinHookFailed("KiUserApcDispatcher", pbKiUserApcDispatcher);
3231 offJmpBack += cbInstr;
3232 }
3233
3234 /* Assemble the code for resuming the call.*/
3235 *(PFNRT *)&g_pfnKiUserApcDispatcherReal = (PFNRT)(uintptr_t)&g_abSupHardReadWriteExecPage[offExecPage];
3236
3237 memcpy(&g_abSupHardReadWriteExecPage[offExecPage], pbKiUserApcDispatcher, offJmpBack);
3238 offExecPage += offJmpBack;
3239
3240 g_abSupHardReadWriteExecPage[offExecPage++] = 0xe9; /* jmp rel32 */
3241 *(uint32_t *)&g_abSupHardReadWriteExecPage[offExecPage] = (uintptr_t)&pbKiUserApcDispatcher[offJmpBack]
3242 - (uintptr_t)&g_abSupHardReadWriteExecPage[offExecPage + 4];
3243 offExecPage = RT_ALIGN_32(offExecPage + 4, 16);
3244
3245 /* Assemble the KiUserApcDispatcher patch. */
3246 memcpy(g_abKiUserApcDispatcherPatch, pbKiUserApcDispatcher, sizeof(g_abKiUserApcDispatcherPatch));
3247 Assert(offJmpBack >= 5);
3248 g_abKiUserApcDispatcherPatch[0] = 0xe9;
3249 *(uint32_t *)&g_abKiUserApcDispatcherPatch[1] = (uintptr_t)supR3HardenedMonitor_KiUserApcDispatcher - (uintptr_t)&pbKiUserApcDispatcher[1+4];
3250#endif
3251
3252#ifndef VBOX_WITHOUT_HARDENDED_XCPT_LOGGING
3253 /*
3254 * Hook #4 - KiUserExceptionDispatcher
3255 * Purpose: Logging crashes.
3256 *
3257 * This differs from the first function in that is no a system call and
3258 * we're at the mercy of the handwritten assembly. This is not mandatory,
3259 * so we ignore failures here.
3260 */
3261 uint8_t * const pbKiUserExceptionDispatcher = (uint8_t *)(uintptr_t)pfnKiUserExceptionDispatcher;
3262 g_pbKiUserExceptionDispatcher = pbKiUserExceptionDispatcher;
3263 memcpy(g_abKiUserExceptionDispatcherPatch, pbKiUserExceptionDispatcher, sizeof(g_abKiUserExceptionDispatcherPatch));
3264
3265# ifdef RT_ARCH_AMD64
3266 /*
3267 * Patch 64-bit hosts.
3268 *
3269 * Assume the following sequence and replacing the loaded Wow64PrepareForException
3270 * function pointer with our callback:
3271 * cld
3272 * mov rax, Wow64PrepareForException ; Wow64PrepareForException(PCONTEXT, PEXCEPTION_RECORD)
3273 * test rax, rax
3274 * jz skip_wow64_callout
3275 * <do_callout_thru_rax>
3276 * (We're not a WOW64 process, so the callout should normally never happen.)
3277 */
3278 if ( pbKiUserExceptionDispatcher[ 0] == 0xfc /* CLD */
3279 && pbKiUserExceptionDispatcher[ 1] == 0x48 /* MOV RAX, symbol wrt rip */
3280 && pbKiUserExceptionDispatcher[ 2] == 0x8b
3281 && pbKiUserExceptionDispatcher[ 3] == 0x05
3282 && pbKiUserExceptionDispatcher[ 8] == 0x48 /* TEST RAX, RAX */
3283 && pbKiUserExceptionDispatcher[ 9] == 0x85
3284 && pbKiUserExceptionDispatcher[10] == 0xc0
3285 && pbKiUserExceptionDispatcher[11] == 0x74)
3286 {
3287 /* Assemble the KiUserExceptionDispatcher patch. */
3288 g_abKiUserExceptionDispatcherPatch[1] = 0x48; /* MOV RAX, supR3HardenedMonitor_KiUserExceptionDispatcher */
3289 g_abKiUserExceptionDispatcherPatch[2] = 0xb8;
3290 *(uint64_t *)&g_abKiUserExceptionDispatcherPatch[3] = (uint64_t)supR3HardenedMonitor_KiUserExceptionDispatcher;
3291 g_abKiUserExceptionDispatcherPatch[11] = 0x90; /* NOP (was JZ) */
3292 g_abKiUserExceptionDispatcherPatch[12] = 0x90; /* NOP (was DISP8 of JZ) */
3293 }
3294 else
3295 SUP_DPRINTF(("supR3HardenedWinInstallHooks: failed to patch KiUserExceptionDispatcher (%.20Rhxs)\n",
3296 pbKiUserExceptionDispatcher));
3297# else
3298 /*
3299 * Patch 32-bit hosts.
3300 */
3301 /* Just use the disassembler to skip 5 bytes or more. */
3302 offJmpBack = 0;
3303 while (offJmpBack < 5)
3304 {
3305 cbInstr = 1;
3306 int rc = DISInstr(pbKiUserExceptionDispatcher + offJmpBack, DISCPUMODE_32BIT, &Dis, &cbInstr);
3307 if ( RT_FAILURE(rc)
3308 || (Dis.pCurInstr->fOpType & (DISOPTYPE_CONTROLFLOW)) )
3309 {
3310 SUP_DPRINTF(("supR3HardenedWinInstallHooks: failed to patch KiUserExceptionDispatcher (off %#x in %.20Rhxs)\n",
3311 offJmpBack, pbKiUserExceptionDispatcher));
3312 break;
3313 }
3314 offJmpBack += cbInstr;
3315 }
3316 if (offJmpBack >= 5)
3317 {
3318 /* Assemble the code for resuming the call.*/
3319 *(PFNRT *)&g_pfnKiUserExceptionDispatcherReal = (PFNRT)(uintptr_t)&g_abSupHardReadWriteExecPage[offExecPage];
3320
3321 memcpy(&g_abSupHardReadWriteExecPage[offExecPage], pbKiUserExceptionDispatcher, offJmpBack);
3322 offExecPage += offJmpBack;
3323
3324 g_abSupHardReadWriteExecPage[offExecPage++] = 0xe9; /* jmp rel32 */
3325 *(uint32_t *)&g_abSupHardReadWriteExecPage[offExecPage] = (uintptr_t)&pbKiUserExceptionDispatcher[offJmpBack]
3326 - (uintptr_t)&g_abSupHardReadWriteExecPage[offExecPage + 4];
3327 offExecPage = RT_ALIGN_32(offExecPage + 4, 16);
3328
3329 /* Assemble the KiUserExceptionDispatcher patch. */
3330 memcpy(g_abKiUserExceptionDispatcherPatch, pbKiUserExceptionDispatcher, sizeof(g_abKiUserExceptionDispatcherPatch));
3331 Assert(offJmpBack >= 5);
3332 g_abKiUserExceptionDispatcherPatch[0] = 0xe9;
3333 *(uint32_t *)&g_abKiUserExceptionDispatcherPatch[1] = (uintptr_t)supR3HardenedMonitor_KiUserExceptionDispatcher - (uintptr_t)&pbKiUserExceptionDispatcher[1+4];
3334 }
3335# endif
3336#endif /* !VBOX_WITHOUT_HARDENDED_XCPT_LOGGING */
3337
3338 /*
3339 * Seal the rwx page.
3340 */
3341 SUPR3HARDENED_ASSERT_NT_SUCCESS(supR3HardenedWinProtectMemory(g_abSupHardReadWriteExecPage, PAGE_SIZE, PAGE_EXECUTE_READ));
3342
3343 /*
3344 * Install the patches.
3345 */
3346 supR3HardenedWinReInstallHooks(true /*fFirstCall*/);
3347}
3348
3349
3350
3351
3352
3353
3354/*
3355 *
3356 * T h r e a d c r e a t i o n c o n t r o l
3357 * T h r e a d c r e a t i o n c o n t r o l
3358 * T h r e a d c r e a t i o n c o n t r o l
3359 *
3360 */
3361
3362
3363/**
3364 * Common code used for child and parent to make new threads exit immediately.
3365 *
3366 * This patches the LdrInitializeThunk code to call NtTerminateThread with
3367 * STATUS_SUCCESS instead of doing the NTDLL initialization.
3368 *
3369 * @returns VBox status code.
3370 * @param hProcess The process to do this to.
3371 * @param pvLdrInitThunk The address of the LdrInitializeThunk code to
3372 * override.
3373 * @param pvNtTerminateThread The address of the NtTerminateThread function in
3374 * the NTDLL instance we're patching. (Must be +/-
3375 * 2GB from the thunk code.)
3376 * @param pabBackup Where to back up the original instruction bytes
3377 * at pvLdrInitThunk.
3378 * @param cbBackup The size of the backup area. Must be 16 bytes.
3379 * @param pErrInfo Where to return extended error information.
3380 * Optional.
3381 */
3382static int supR3HardNtDisableThreadCreationEx(HANDLE hProcess, void *pvLdrInitThunk, void *pvNtTerminateThread,
3383 uint8_t *pabBackup, size_t cbBackup, PRTERRINFO pErrInfo)
3384{
3385 SUP_DPRINTF(("supR3HardNtDisableThreadCreation: pvLdrInitThunk=%p pvNtTerminateThread=%p\n", pvLdrInitThunk, pvNtTerminateThread));
3386 SUPR3HARDENED_ASSERT(cbBackup == 16);
3387 SUPR3HARDENED_ASSERT(RT_ABS((intptr_t)pvLdrInitThunk - (intptr_t)pvNtTerminateThread) < 16*_1M);
3388
3389 /*
3390 * Back up the thunk code.
3391 */
3392 SIZE_T cbIgnored;
3393 NTSTATUS rcNt = NtReadVirtualMemory(hProcess, pvLdrInitThunk, pabBackup, cbBackup, &cbIgnored);
3394 if (!NT_SUCCESS(rcNt))
3395 return RTErrInfoSetF(pErrInfo, VERR_GENERAL_FAILURE,
3396 "supR3HardNtDisableThreadCreation: NtReadVirtualMemory/LdrInitializeThunk failed: %#x", rcNt);
3397
3398 /*
3399 * Cook up replacement code that calls NtTerminateThread.
3400 */
3401 uint8_t abReplacement[16];
3402 memcpy(abReplacement, pabBackup, sizeof(abReplacement));
3403
3404#ifdef RT_ARCH_AMD64
3405 abReplacement[0] = 0x31; /* xor ecx, ecx */
3406 abReplacement[1] = 0xc9;
3407 abReplacement[2] = 0x31; /* xor edx, edx */
3408 abReplacement[3] = 0xd2;
3409 abReplacement[4] = 0xe8; /* call near NtTerminateThread */
3410 *(int32_t *)&abReplacement[5] = (int32_t)((uintptr_t)pvNtTerminateThread - ((uintptr_t)pvLdrInitThunk + 9));
3411 abReplacement[9] = 0xcc; /* int3 */
3412#elif defined(RT_ARCH_X86)
3413 abReplacement[0] = 0x6a; /* push 0 */
3414 abReplacement[1] = 0x00;
3415 abReplacement[2] = 0x6a; /* push 0 */
3416 abReplacement[3] = 0x00;
3417 abReplacement[4] = 0xe8; /* call near NtTerminateThread */
3418 *(int32_t *)&abReplacement[5] = (int32_t)((uintptr_t)pvNtTerminateThread - ((uintptr_t)pvLdrInitThunk + 9));
3419 abReplacement[9] = 0xcc; /* int3 */
3420#else
3421# error "Unsupported arch."
3422#endif
3423
3424 /*
3425 * Install the replacment code.
3426 */
3427 PVOID pvProt = pvLdrInitThunk;
3428 SIZE_T cbProt = cbBackup;
3429 ULONG fOldProt = 0;
3430 rcNt = NtProtectVirtualMemory(hProcess, &pvProt, &cbProt, PAGE_EXECUTE_READWRITE, &fOldProt);
3431 if (!NT_SUCCESS(rcNt))
3432 return RTErrInfoSetF(pErrInfo, VERR_GENERAL_FAILURE,
3433 "supR3HardNtDisableThreadCreationEx: NtProtectVirtualMemory/LdrInitializeThunk failed: %#x", rcNt);
3434
3435 rcNt = NtWriteVirtualMemory(hProcess, pvLdrInitThunk, abReplacement, sizeof(abReplacement), &cbIgnored);
3436 if (!NT_SUCCESS(rcNt))
3437 return RTErrInfoSetF(pErrInfo, VERR_GENERAL_FAILURE,
3438 "supR3HardNtDisableThreadCreationEx: NtWriteVirtualMemory/LdrInitializeThunk failed: %#x", rcNt);
3439
3440 pvProt = pvLdrInitThunk;
3441 cbProt = cbBackup;
3442 rcNt = NtProtectVirtualMemory(hProcess, &pvProt, &cbProt, fOldProt, &fOldProt);
3443 if (!NT_SUCCESS(rcNt))
3444 return RTErrInfoSetF(pErrInfo, VERR_GENERAL_FAILURE,
3445 "supR3HardNtDisableThreadCreationEx: NtProtectVirtualMemory/LdrInitializeThunk/2 failed: %#x", rcNt);
3446
3447 return VINF_SUCCESS;
3448}
3449
3450
3451/**
3452 * Undo the effects of supR3HardNtDisableThreadCreationEx.
3453 *
3454 * @returns VBox status code.
3455 * @param hProcess The process to do this to.
3456 * @param pvLdrInitThunk The address of the LdrInitializeThunk code to
3457 * override.
3458 * @param pabBackup Where to back up the original instruction bytes
3459 * at pvLdrInitThunk.
3460 * @param cbBackup The size of the backup area. Must be 16 bytes.
3461 * @param pErrInfo Where to return extended error information.
3462 * Optional.
3463 */
3464static int supR3HardNtEnableThreadCreationEx(HANDLE hProcess, void *pvLdrInitThunk, uint8_t const *pabBackup, size_t cbBackup,
3465 PRTERRINFO pErrInfo)
3466{
3467 SUP_DPRINTF(("supR3HardNtEnableThreadCreationEx:\n"));
3468 SUPR3HARDENED_ASSERT(cbBackup == 16);
3469
3470 PVOID pvProt = pvLdrInitThunk;
3471 SIZE_T cbProt = cbBackup;
3472 ULONG fOldProt = 0;
3473 NTSTATUS rcNt = NtProtectVirtualMemory(hProcess, &pvProt, &cbProt, PAGE_EXECUTE_READWRITE, &fOldProt);
3474 if (!NT_SUCCESS(rcNt))
3475 return RTErrInfoSetF(pErrInfo, VERR_GENERAL_FAILURE,
3476 "supR3HardNtEnableThreadCreationEx: NtProtectVirtualMemory/LdrInitializeThunk failed: %#x", rcNt);
3477
3478 SIZE_T cbIgnored;
3479 rcNt = NtWriteVirtualMemory(hProcess, pvLdrInitThunk, pabBackup, cbBackup, &cbIgnored);
3480 if (!NT_SUCCESS(rcNt))
3481 return RTErrInfoSetF(pErrInfo, VERR_GENERAL_FAILURE,
3482 "supR3HardNtEnableThreadCreationEx: NtWriteVirtualMemory/LdrInitializeThunk[restore] failed: %#x",
3483 rcNt);
3484
3485 pvProt = pvLdrInitThunk;
3486 cbProt = cbBackup;
3487 rcNt = NtProtectVirtualMemory(hProcess, &pvProt, &cbProt, fOldProt, &fOldProt);
3488 if (!NT_SUCCESS(rcNt))
3489 return RTErrInfoSetF(pErrInfo, VERR_GENERAL_FAILURE,
3490 "supR3HardNtEnableThreadCreationEx: NtProtectVirtualMemory/LdrInitializeThunk[restore] failed: %#x",
3491 rcNt);
3492
3493 return VINF_SUCCESS;
3494}
3495
3496
3497/**
3498 * Disable thread creation for the current process.
3499 *
3500 * @remarks Doesn't really disables it, just makes the threads exit immediately
3501 * without executing any real code.
3502 */
3503static void supR3HardenedWinDisableThreadCreation(void)
3504{
3505 /* Cannot use the imported NtTerminateThread as it's pointing to our own
3506 syscall assembly code. */
3507 static PFNRT s_pfnNtTerminateThread = NULL;
3508 if (s_pfnNtTerminateThread == NULL)
3509 s_pfnNtTerminateThread = supR3HardenedWinGetRealDllSymbol("ntdll.dll", "NtTerminateThread");
3510 SUPR3HARDENED_ASSERT(s_pfnNtTerminateThread);
3511
3512 int rc = supR3HardNtDisableThreadCreationEx(NtCurrentProcess(),
3513 (void *)(uintptr_t)&LdrInitializeThunk,
3514 (void *)(uintptr_t)s_pfnNtTerminateThread,
3515 g_abLdrInitThunkSelfBackup, sizeof(g_abLdrInitThunkSelfBackup),
3516 NULL /* pErrInfo*/);
3517 g_fSupInitThunkSelfPatched = RT_SUCCESS(rc);
3518}
3519
3520
3521/**
3522 * Undoes the effects of supR3HardenedWinDisableThreadCreation.
3523 */
3524DECLHIDDEN(void) supR3HardenedWinEnableThreadCreation(void)
3525{
3526 if (g_fSupInitThunkSelfPatched)
3527 {
3528 int rc = supR3HardNtEnableThreadCreationEx(NtCurrentProcess(),
3529 (void *)(uintptr_t)&LdrInitializeThunk,
3530 g_abLdrInitThunkSelfBackup, sizeof(g_abLdrInitThunkSelfBackup),
3531 RTErrInfoInitStatic(&g_ErrInfoStatic));
3532 if (RT_FAILURE(rc))
3533 supR3HardenedError(rc, true /*fFatal*/, "%s", g_ErrInfoStatic.szMsg);
3534 g_fSupInitThunkSelfPatched = false;
3535 }
3536}
3537
3538
3539
3540
3541/*
3542 *
3543 * R e s p a w n
3544 * R e s p a w n
3545 * R e s p a w n
3546 *
3547 */
3548
3549
3550/**
3551 * Gets the SID of the user associated with the process.
3552 *
3553 * @returns @c true if we've got a login SID, @c false if not.
3554 * @param pSidUser Where to return the user SID.
3555 * @param cbSidUser The size of the user SID buffer.
3556 * @param pSidLogin Where to return the login SID.
3557 * @param cbSidLogin The size of the login SID buffer.
3558 */
3559static bool supR3HardNtChildGetUserAndLogSids(PSID pSidUser, ULONG cbSidUser, PSID pSidLogin, ULONG cbSidLogin)
3560{
3561 HANDLE hToken;
3562 SUPR3HARDENED_ASSERT_NT_SUCCESS(NtOpenProcessToken(NtCurrentProcess(), TOKEN_QUERY, &hToken));
3563 union
3564 {
3565 TOKEN_USER UserInfo;
3566 TOKEN_GROUPS Groups;
3567 uint8_t abPadding[4096];
3568 } uBuf;
3569 ULONG cbRet = 0;
3570 SUPR3HARDENED_ASSERT_NT_SUCCESS(NtQueryInformationToken(hToken, TokenUser, &uBuf, sizeof(uBuf), &cbRet));
3571 SUPR3HARDENED_ASSERT_NT_SUCCESS(RtlCopySid(cbSidUser, pSidUser, uBuf.UserInfo.User.Sid));
3572
3573 bool fLoginSid = false;
3574 NTSTATUS rcNt = NtQueryInformationToken(hToken, TokenLogonSid, &uBuf, sizeof(uBuf), &cbRet);
3575 if (NT_SUCCESS(rcNt))
3576 {
3577 for (DWORD i = 0; i < uBuf.Groups.GroupCount; i++)
3578 if ((uBuf.Groups.Groups[i].Attributes & SE_GROUP_LOGON_ID) == SE_GROUP_LOGON_ID)
3579 {
3580 SUPR3HARDENED_ASSERT_NT_SUCCESS(RtlCopySid(cbSidLogin, pSidLogin, uBuf.Groups.Groups[i].Sid));
3581 fLoginSid = true;
3582 break;
3583 }
3584 }
3585
3586 SUPR3HARDENED_ASSERT_NT_SUCCESS(NtClose(hToken));
3587
3588 return fLoginSid;
3589}
3590
3591
3592/**
3593 * Build security attributes for the process or the primary thread (@a fProcess)
3594 *
3595 * Process DACLs can be bypassed using the SeDebugPrivilege (generally available
3596 * to admins, i.e. normal windows users), or by taking ownership and/or
3597 * modifying the DACL. However, it restricts
3598 *
3599 * @param pSecAttrs Where to return the security attributes.
3600 * @param pCleanup Cleanup record.
3601 * @param fProcess Set if it's for the process, clear if it's for
3602 * the primary thread.
3603 */
3604static void supR3HardNtChildInitSecAttrs(PSECURITY_ATTRIBUTES pSecAttrs, PMYSECURITYCLEANUP pCleanup, bool fProcess)
3605{
3606 /*
3607 * Safe return values.
3608 */
3609 suplibHardenedMemSet(pCleanup, 0, sizeof(*pCleanup));
3610
3611 pSecAttrs->nLength = sizeof(*pSecAttrs);
3612 pSecAttrs->bInheritHandle = FALSE;
3613 pSecAttrs->lpSecurityDescriptor = NULL;
3614
3615/** @todo This isn't at all complete, just sketches... */
3616
3617 /*
3618 * Create an ACL detailing the access of the above groups.
3619 */
3620 SUPR3HARDENED_ASSERT_NT_SUCCESS(RtlCreateAcl(&pCleanup->Acl.AclHdr, sizeof(pCleanup->Acl), ACL_REVISION));
3621
3622 ULONG fDeny = DELETE | WRITE_DAC | WRITE_OWNER;
3623 ULONG fAllow = SYNCHRONIZE | READ_CONTROL;
3624 ULONG fAllowLogin = SYNCHRONIZE | READ_CONTROL;
3625 if (fProcess)
3626 {
3627 fDeny |= PROCESS_CREATE_THREAD | PROCESS_SET_SESSIONID | PROCESS_VM_OPERATION | PROCESS_VM_WRITE
3628 | PROCESS_CREATE_PROCESS | PROCESS_DUP_HANDLE | PROCESS_SET_QUOTA
3629 | PROCESS_SET_INFORMATION | PROCESS_SUSPEND_RESUME;
3630 fAllow |= PROCESS_TERMINATE | PROCESS_VM_READ | PROCESS_QUERY_INFORMATION;
3631 fAllowLogin |= PROCESS_TERMINATE | PROCESS_VM_READ | PROCESS_QUERY_INFORMATION;
3632 if (g_uNtVerCombined >= SUP_MAKE_NT_VER_SIMPLE(6, 0)) /* Introduced in Vista. */
3633 {
3634 fAllow |= PROCESS_QUERY_LIMITED_INFORMATION;
3635 fAllowLogin |= PROCESS_QUERY_LIMITED_INFORMATION;
3636 }
3637 if (g_uNtVerCombined >= SUP_MAKE_NT_VER_SIMPLE(6, 3)) /* Introduced in Windows 8.1. */
3638 fAllow |= PROCESS_SET_LIMITED_INFORMATION;
3639 }
3640 else
3641 {
3642 fDeny |= THREAD_SUSPEND_RESUME | THREAD_SET_CONTEXT | THREAD_SET_INFORMATION | THREAD_SET_THREAD_TOKEN
3643 | THREAD_IMPERSONATE | THREAD_DIRECT_IMPERSONATION;
3644 fAllow |= THREAD_GET_CONTEXT | THREAD_QUERY_INFORMATION;
3645 fAllowLogin |= THREAD_GET_CONTEXT | THREAD_QUERY_INFORMATION;
3646 if (g_uNtVerCombined >= SUP_MAKE_NT_VER_SIMPLE(6, 0)) /* Introduced in Vista. */
3647 {
3648 fAllow |= THREAD_QUERY_LIMITED_INFORMATION | THREAD_SET_LIMITED_INFORMATION;
3649 fAllowLogin |= THREAD_QUERY_LIMITED_INFORMATION;
3650 }
3651
3652 }
3653 fDeny |= ~fAllow & (SPECIFIC_RIGHTS_ALL | STANDARD_RIGHTS_ALL);
3654
3655 /* Deny everyone access to bad bits. */
3656#if 1
3657 SID_IDENTIFIER_AUTHORITY SIDAuthWorld = SECURITY_WORLD_SID_AUTHORITY;
3658 SUPR3HARDENED_ASSERT_NT_SUCCESS(RtlInitializeSid(&pCleanup->Everyone.Sid, &SIDAuthWorld, 1));
3659 *RtlSubAuthoritySid(&pCleanup->Everyone.Sid, 0) = SECURITY_WORLD_RID;
3660 SUPR3HARDENED_ASSERT_NT_SUCCESS(RtlAddAccessDeniedAce(&pCleanup->Acl.AclHdr, ACL_REVISION,
3661 fDeny, &pCleanup->Everyone.Sid));
3662#endif
3663
3664#if 0
3665 /* Grant some access to the owner - doesn't work. */
3666 SID_IDENTIFIER_AUTHORITY SIDAuthCreator = SECURITY_CREATOR_SID_AUTHORITY;
3667 SUPR3HARDENED_ASSERT_NT_SUCCESS(RtlInitializeSid(&pCleanup->Owner.Sid, &SIDAuthCreator, 1));
3668 *RtlSubAuthoritySid(&pCleanup->Owner.Sid, 0) = SECURITY_CREATOR_OWNER_RID;
3669
3670 SUPR3HARDENED_ASSERT_NT_SUCCESS(RtlAddAccessDeniedAce(&pCleanup->Acl.AclHdr, ACL_REVISION,
3671 fDeny, &pCleanup->Owner.Sid));
3672 SUPR3HARDENED_ASSERT_NT_SUCCESS(RtlAddAccessAllowedAce(&pCleanup->Acl.AclHdr, ACL_REVISION,
3673 fAllow, &pCleanup->Owner.Sid));
3674#endif
3675
3676#if 1
3677 bool fHasLoginSid = supR3HardNtChildGetUserAndLogSids(&pCleanup->User.Sid, sizeof(pCleanup->User),
3678 &pCleanup->Login.Sid, sizeof(pCleanup->Login));
3679
3680# if 1
3681 /* Grant minimal access to the user. */
3682 SUPR3HARDENED_ASSERT_NT_SUCCESS(RtlAddAccessDeniedAce(&pCleanup->Acl.AclHdr, ACL_REVISION,
3683 fDeny, &pCleanup->User.Sid));
3684 SUPR3HARDENED_ASSERT_NT_SUCCESS(RtlAddAccessAllowedAce(&pCleanup->Acl.AclHdr, ACL_REVISION,
3685 fAllow, &pCleanup->User.Sid));
3686# endif
3687
3688# if 1
3689 /* Grant very limited access to the login sid. */
3690 if (fHasLoginSid)
3691 {
3692 SUPR3HARDENED_ASSERT_NT_SUCCESS(RtlAddAccessAllowedAce(&pCleanup->Acl.AclHdr, ACL_REVISION,
3693 fAllowLogin, &pCleanup->Login.Sid));
3694 }
3695# endif
3696
3697#endif
3698
3699 /*
3700 * Create a security descriptor with the above ACL.
3701 */
3702 PSECURITY_DESCRIPTOR pSecDesc = (PSECURITY_DESCRIPTOR)RTMemAllocZ(SECURITY_DESCRIPTOR_MIN_LENGTH);
3703 pCleanup->pSecDesc = pSecDesc;
3704
3705 SUPR3HARDENED_ASSERT_NT_SUCCESS(RtlCreateSecurityDescriptor(pSecDesc, SECURITY_DESCRIPTOR_REVISION));
3706 SUPR3HARDENED_ASSERT_NT_SUCCESS(RtlSetDaclSecurityDescriptor(pSecDesc, TRUE /*fDaclPresent*/, &pCleanup->Acl.AclHdr,
3707 FALSE /*fDaclDefaulted*/));
3708 pSecAttrs->lpSecurityDescriptor = pSecDesc;
3709}
3710
3711
3712/**
3713 * Predicate function which tests whether @a ch is a argument separator
3714 * character.
3715 *
3716 * @returns True/false.
3717 * @param ch The character to examine.
3718 */
3719DECLINLINE(bool) suplibCommandLineIsArgSeparator(int ch)
3720{
3721 return ch == ' '
3722 || ch == '\t'
3723 || ch == '\n'
3724 || ch == '\r';
3725}
3726
3727
3728/**
3729 * Construct the new command line.
3730 *
3731 * Since argc/argv are both derived from GetCommandLineW (see
3732 * suplibHardenedWindowsMain), we skip the argument by argument UTF-8 -> UTF-16
3733 * conversion and quoting by going to the original source.
3734 *
3735 * The executable name, though, is replaced in case it's not a fullly
3736 * qualified path.
3737 *
3738 * The re-spawn indicator is added immediately after the executable name
3739 * so that we don't get tripped up missing close quote chars in the last
3740 * argument.
3741 *
3742 * @returns Pointer to a command line string (heap).
3743 * @param pString Unicode string structure to initialize to the
3744 * command line. Optional.
3745 * @param iWhich Which respawn we're to check for, 1 being the first
3746 * one, and 2 the second and final.
3747 */
3748static PRTUTF16 supR3HardNtChildConstructCmdLine(PUNICODE_STRING pString, int iWhich)
3749{
3750 SUPR3HARDENED_ASSERT(iWhich == 1 || iWhich == 2);
3751
3752 /*
3753 * Get the command line and skip the executable name.
3754 */
3755 PUNICODE_STRING pCmdLineStr = &NtCurrentPeb()->ProcessParameters->CommandLine;
3756 PCRTUTF16 pawcArgs = pCmdLineStr->Buffer;
3757 uint32_t cwcArgs = pCmdLineStr->Length / sizeof(WCHAR);
3758
3759 /* Skip leading space (shouldn't be any, but whatever). */
3760 while (cwcArgs > 0 && suplibCommandLineIsArgSeparator(*pawcArgs) )
3761 cwcArgs--, pawcArgs++;
3762 SUPR3HARDENED_ASSERT(cwcArgs > 0 && *pawcArgs != '\0');
3763
3764 /* Walk to the end of it. */
3765 int fQuoted = false;
3766 do
3767 {
3768 if (*pawcArgs == '"')
3769 {
3770 fQuoted = !fQuoted;
3771 cwcArgs--; pawcArgs++;
3772 }
3773 else if (*pawcArgs != '\\' || (pawcArgs[1] != '\\' && pawcArgs[1] != '"'))
3774 cwcArgs--, pawcArgs++;
3775 else
3776 {
3777 unsigned cSlashes = 0;
3778 do
3779 {
3780 cSlashes++;
3781 cwcArgs--;
3782 pawcArgs++;
3783 }
3784 while (cwcArgs > 0 && *pawcArgs == '\\');
3785 if (cwcArgs > 0 && *pawcArgs == '"' && (cSlashes & 1))
3786 cwcArgs--, pawcArgs++; /* odd number of slashes == escaped quote */
3787 }
3788 } while (cwcArgs > 0 && (fQuoted || !suplibCommandLineIsArgSeparator(*pawcArgs)));
3789
3790 /* Skip trailing spaces. */
3791 while (cwcArgs > 0 && suplibCommandLineIsArgSeparator(*pawcArgs))
3792 cwcArgs--, pawcArgs++;
3793
3794 /*
3795 * Allocate a new buffer.
3796 */
3797 AssertCompile(sizeof(SUPR3_RESPAWN_1_ARG0) == sizeof(SUPR3_RESPAWN_2_ARG0));
3798 size_t cwcCmdLine = (sizeof(SUPR3_RESPAWN_1_ARG0) - 1) / sizeof(SUPR3_RESPAWN_1_ARG0[0]) /* Respawn exe name. */
3799 + !!cwcArgs + cwcArgs; /* if arguments present, add space + arguments. */
3800 if (cwcCmdLine * sizeof(WCHAR) >= 0xfff0)
3801 supR3HardenedFatalMsg("supR3HardNtChildConstructCmdLine", kSupInitOp_Misc, VERR_OUT_OF_RANGE,
3802 "Command line is too long (%u chars)!", cwcCmdLine);
3803
3804 PRTUTF16 pwszCmdLine = (PRTUTF16)RTMemAlloc((cwcCmdLine + 1) * sizeof(RTUTF16));
3805 SUPR3HARDENED_ASSERT(pwszCmdLine != NULL);
3806
3807 /*
3808 * Construct the new command line.
3809 */
3810 PRTUTF16 pwszDst = pwszCmdLine;
3811 for (const char *pszSrc = iWhich == 1 ? SUPR3_RESPAWN_1_ARG0 : SUPR3_RESPAWN_2_ARG0; *pszSrc; pszSrc++)
3812 *pwszDst++ = *pszSrc;
3813
3814 if (cwcArgs)
3815 {
3816 *pwszDst++ = ' ';
3817 suplibHardenedMemCopy(pwszDst, pawcArgs, cwcArgs * sizeof(RTUTF16));
3818 pwszDst += cwcArgs;
3819 }
3820
3821 *pwszDst = '\0';
3822 SUPR3HARDENED_ASSERT((uintptr_t)(pwszDst - pwszCmdLine) == cwcCmdLine);
3823
3824 if (pString)
3825 {
3826 pString->Buffer = pwszCmdLine;
3827 pString->Length = (USHORT)(cwcCmdLine * sizeof(WCHAR));
3828 pString->MaximumLength = pString->Length + sizeof(WCHAR);
3829 }
3830 return pwszCmdLine;
3831}
3832
3833
3834/**
3835 * Terminates the child process.
3836 *
3837 * @param hProcess The process handle.
3838 * @param pszWhere Who's having child rasing troubles.
3839 * @param rc The status code to report.
3840 * @param pszFormat The message format string.
3841 * @param ... Message format arguments.
3842 */
3843static void supR3HardenedWinKillChild(HANDLE hProcess, const char *pszWhere, int rc, const char *pszFormat, ...)
3844{
3845 /*
3846 * Terminate the process ASAP and display error.
3847 */
3848 NtTerminateProcess(hProcess, RTEXITCODE_FAILURE);
3849
3850 va_list va;
3851 va_start(va, pszFormat);
3852 supR3HardenedErrorV(rc, false /*fFatal*/, pszFormat, va);
3853 va_end(va);
3854
3855 /*
3856 * Wait for the process to really go away.
3857 */
3858 PROCESS_BASIC_INFORMATION BasicInfo;
3859 NTSTATUS rcNtExit = NtQueryInformationProcess(hProcess, ProcessBasicInformation, &BasicInfo, sizeof(BasicInfo), NULL);
3860 bool fExitOk = NT_SUCCESS(rcNtExit) && BasicInfo.ExitStatus != STATUS_PENDING;
3861 if (!fExitOk)
3862 {
3863 NTSTATUS rcNtWait;
3864 uint64_t uMsTsStart = supR3HardenedWinGetMilliTS();
3865 do
3866 {
3867 NtTerminateProcess(hProcess, DBG_TERMINATE_PROCESS);
3868
3869 LARGE_INTEGER Timeout;
3870 Timeout.QuadPart = -20000000; /* 2 second */
3871 rcNtWait = NtWaitForSingleObject(hProcess, TRUE /*Alertable*/, &Timeout);
3872
3873 rcNtExit = NtQueryInformationProcess(hProcess, ProcessBasicInformation, &BasicInfo, sizeof(BasicInfo), NULL);
3874 fExitOk = NT_SUCCESS(rcNtExit) && BasicInfo.ExitStatus != STATUS_PENDING;
3875 } while ( !fExitOk
3876 && ( rcNtWait == STATUS_TIMEOUT
3877 || rcNtWait == STATUS_USER_APC
3878 || rcNtWait == STATUS_ALERTED)
3879 && supR3HardenedWinGetMilliTS() - uMsTsStart < 60 * 1000);
3880 if (fExitOk)
3881 supR3HardenedError(rc, false /*fFatal*/,
3882 "NtDuplicateObject failed and we failed to kill child: rc=%u (%#x) rcNtWait=%#x hProcess=%p\n",
3883 rc, rc, rcNtWait, hProcess);
3884 }
3885
3886 /*
3887 * Final error message.
3888 */
3889 va_start(va, pszFormat);
3890 supR3HardenedFatalMsgV(pszWhere, kSupInitOp_Misc, rc, pszFormat, va);
3891 /* not reached */
3892}
3893
3894
3895/**
3896 * Checks the child process when hEvtParent is signalled.
3897 *
3898 * This will read the request data from the child and check it against expected
3899 * request. If an error is signalled, we'll raise it and make sure the child
3900 * terminates before terminating the calling process.
3901 *
3902 * @param pThis The child process data structure.
3903 * @param enmExpectedRequest The expected child request.
3904 * @param pszWhat What we're waiting for.
3905 */
3906static void supR3HardNtChildProcessRequest(PSUPR3HARDNTCHILD pThis, SUPR3WINCHILDREQ enmExpectedRequest, const char *pszWhat)
3907{
3908 /*
3909 * Read the process parameters from the child.
3910 */
3911 uintptr_t uChildAddr = (uintptr_t)pThis->Peb.ImageBaseAddress
3912 + ((uintptr_t)&g_ProcParams - (uintptr_t)NtCurrentPeb()->ImageBaseAddress);
3913 SIZE_T cbIgnored = 0;
3914 RT_ZERO(pThis->ProcParams);
3915 NTSTATUS rcNt = NtReadVirtualMemory(pThis->hProcess, (PVOID)uChildAddr,
3916 &pThis->ProcParams, sizeof(pThis->ProcParams), &cbIgnored);
3917 if (!NT_SUCCESS(rcNt))
3918 supR3HardenedWinKillChild(pThis, "supR3HardNtChildProcessRequest", rcNt,
3919 "NtReadVirtualMemory(,%p,) failed reading child process status: %#x\n", uChildAddr, rcNt);
3920
3921 /*
3922 * Is it the expected request?
3923 */
3924 if (pThis->ProcParams.enmRequest == enmExpectedRequest)
3925 return;
3926
3927 /*
3928 * No, not the expected request. If it's an error request, tell the child
3929 * to terminate itself, otherwise we'll have to terminate it.
3930 */
3931 pThis->ProcParams.szErrorMsg[sizeof(pThis->ProcParams.szErrorMsg) - 1] = '\0';
3932 pThis->ProcParams.szWhere[sizeof(pThis->ProcParams.szWhere) - 1] = '\0';
3933 SUP_DPRINTF(("supR3HardenedWinCheckChild: enmRequest=%d rc=%d enmWhat=%d %s: %s\n",
3934 pThis->ProcParams.enmRequest, pThis->ProcParams.rc, pThis->ProcParams.enmWhat,
3935 pThis->ProcParams.szWhere, pThis->ProcParams.szErrorMsg));
3936
3937 if (pThis->ProcParams.enmRequest != kSupR3WinChildReq_Error)
3938 supR3HardenedWinKillChild(pThis, "supR3HardenedWinCheckChild", VERR_INVALID_PARAMETER,
3939 "Unexpected child request #%d. Was expecting #%d (%s).\n",
3940 pThis->ProcParams.enmRequest, enmExpectedRequest, pszWhat);
3941
3942 rcNt = NtSetEvent(pThis->hEvtChild, NULL);
3943 if (!NT_SUCCESS(rcNt))
3944 supR3HardenedWinKillChild(pThis, "supR3HardNtChildProcessRequest", rcNt, "NtSetEvent failed: %#x\n", rcNt);
3945
3946 /* Wait for it to terminate. */
3947 LARGE_INTEGER Timeout;
3948 Timeout.QuadPart = -50000000; /* 5 seconds */
3949 rcNt = NtWaitForSingleObject(pThis->hProcess, FALSE /*Alertable*/, &Timeout);
3950 if (rcNt != STATUS_WAIT_0)
3951 {
3952 SUP_DPRINTF(("supR3HardNtChildProcessRequest: Child is taking too long to quit (rcWait=%#x), killing it...\n", rcNt));
3953 NtTerminateProcess(pThis->hProcess, DBG_TERMINATE_PROCESS);
3954 }
3955
3956 /*
3957 * Report the error in the same way as it occured in the guest.
3958 */
3959 if (pThis->ProcParams.enmWhat == kSupInitOp_Invalid)
3960 supR3HardenedFatalMsg("supR3HardenedWinCheckChild", kSupInitOp_Misc, pThis->ProcParams.rc,
3961 "%s", pThis->ProcParams.szErrorMsg);
3962 else
3963 supR3HardenedFatalMsg(pThis->ProcParams.szWhere, pThis->ProcParams.enmWhat, pThis->ProcParams.rc,
3964 "%s", pThis->ProcParams.szErrorMsg);
3965}
3966
3967
3968/**
3969 * Waits for the child to make a certain request or terminate.
3970 *
3971 * The stub process will also wait on it's parent to terminate.
3972 * This call will only return if the child made the expected request.
3973 *
3974 * @param pThis The child process data structure.
3975 * @param enmExpectedRequest The child request to wait for.
3976 * @param cMsTimeout The number of milliseconds to wait (at least).
3977 * @param pszWhat What we're waiting for.
3978 */
3979static void supR3HardNtChildWaitFor(PSUPR3HARDNTCHILD pThis, SUPR3WINCHILDREQ enmExpectedRequest, RTMSINTERVAL cMsTimeout,
3980 const char *pszWhat)
3981{
3982 /*
3983 * The wait loop.
3984 * Will return when the expected request arrives.
3985 * Will break out when one of the processes terminates.
3986 */
3987 NTSTATUS rcNtWait;
3988 LARGE_INTEGER Timeout;
3989 uint64_t uMsTsStart = supR3HardenedWinGetMilliTS();
3990 uint64_t cMsElapsed = 0;
3991 for (;;)
3992 {
3993 /*
3994 * Assemble handles to wait for.
3995 */
3996 ULONG cHandles = 1;
3997 HANDLE ahHandles[3];
3998 ahHandles[0] = pThis->hProcess;
3999 if (pThis->hEvtParent)
4000 ahHandles[cHandles++] = pThis->hEvtParent;
4001 if (pThis->hParent)
4002 ahHandles[cHandles++] = pThis->hParent;
4003
4004 /*
4005 * Do the waiting according to the callers wishes.
4006 */
4007 if ( enmExpectedRequest == kSupR3WinChildReq_End
4008 || cMsTimeout == RT_INDEFINITE_WAIT)
4009 rcNtWait = NtWaitForMultipleObjects(cHandles, &ahHandles[0], WaitAnyObject, TRUE /*Alertable*/, NULL /*Timeout*/);
4010 else
4011 {
4012 Timeout.QuadPart = -(int64_t)(cMsTimeout - cMsElapsed) * 10000;
4013 rcNtWait = NtWaitForMultipleObjects(cHandles, &ahHandles[0], WaitAnyObject, TRUE /*Alertable*/, &Timeout);
4014 }
4015
4016 /*
4017 * Process child request.
4018 */
4019 if (rcNtWait == STATUS_WAIT_0 + 1 && pThis->hEvtParent != NULL)
4020 {
4021 supR3HardNtChildProcessRequest(pThis, enmExpectedRequest, pszWhat);
4022 SUP_DPRINTF(("supR3HardNtChildWaitFor: Found expected request %d (%s) after %llu ms.\n",
4023 enmExpectedRequest, pszWhat, supR3HardenedWinGetMilliTS() - uMsTsStart));
4024 return; /* Expected request received. */
4025 }
4026
4027 /*
4028 * Process termination?
4029 */
4030 if ( (ULONG)rcNtWait - (ULONG)STATUS_WAIT_0 < cHandles
4031 || (ULONG)rcNtWait - (ULONG)STATUS_ABANDONED_WAIT_0 < cHandles)
4032 break;
4033
4034 /*
4035 * Check sanity.
4036 */
4037 if ( rcNtWait != STATUS_TIMEOUT
4038 && rcNtWait != STATUS_USER_APC
4039 && rcNtWait != STATUS_ALERTED)
4040 supR3HardenedWinKillChild(pThis, "supR3HardNtChildWaitFor", rcNtWait,
4041 "NtWaitForMultipleObjects returned %#x waiting for #%d (%s)\n",
4042 rcNtWait, enmExpectedRequest, pszWhat);
4043
4044 /*
4045 * Calc elapsed time for the next timeout calculation, checking to see
4046 * if we've timed out already.
4047 */
4048 cMsElapsed = supR3HardenedWinGetMilliTS() - uMsTsStart;
4049 if ( cMsElapsed > cMsTimeout
4050 && cMsTimeout != RT_INDEFINITE_WAIT
4051 && enmExpectedRequest != kSupR3WinChildReq_End)
4052 {
4053 if (rcNtWait == STATUS_USER_APC || rcNtWait == STATUS_ALERTED)
4054 cMsElapsed = cMsTimeout - 1; /* try again */
4055 else
4056 {
4057 /* We timed out. */
4058 supR3HardenedWinKillChild(pThis, "supR3HardNtChildWaitFor", rcNtWait,
4059 "Timed out after %llu ms waiting for child request #%d (%s).\n",
4060 cMsElapsed, enmExpectedRequest, pszWhat);
4061 }
4062 }
4063 }
4064
4065 /*
4066 * Proxy the termination code of the child, if it exited already.
4067 */
4068 PROCESS_BASIC_INFORMATION BasicInfo;
4069 NTSTATUS rcNt1 = NtQueryInformationProcess(pThis->hProcess, ProcessBasicInformation, &BasicInfo, sizeof(BasicInfo), NULL);
4070 NTSTATUS rcNt2 = STATUS_PENDING;
4071 NTSTATUS rcNt3 = STATUS_PENDING;
4072 if ( !NT_SUCCESS(rcNt1)
4073 || BasicInfo.ExitStatus == STATUS_PENDING)
4074 {
4075 rcNt2 = NtTerminateProcess(pThis->hProcess, RTEXITCODE_FAILURE);
4076 Timeout.QuadPart = NT_SUCCESS(rcNt2) ? -20000000 /* 2 sec */ : -1280000 /* 128 ms */;
4077 rcNt3 = NtWaitForSingleObject(pThis->hProcess, FALSE /*Alertable*/, NULL /*Timeout*/);
4078 BasicInfo.ExitStatus = RTEXITCODE_FAILURE;
4079 }
4080
4081 SUP_DPRINTF(("supR3HardNtChildWaitFor[%d]: Quitting: ExitCode=%#x (rcNtWait=%#x, rcNt1=%#x, rcNt2=%#x, rcNt3=%#x, %llu ms, %s);\n",
4082 pThis->iWhich, BasicInfo.ExitStatus, rcNtWait, rcNt1, rcNt2, rcNt3,
4083 supR3HardenedWinGetMilliTS() - uMsTsStart, pszWhat));
4084 suplibHardenedExit((RTEXITCODE)BasicInfo.ExitStatus);
4085}
4086
4087
4088/**
4089 * Closes full access child thread and process handles, making a harmless
4090 * duplicate of the process handle first.
4091 *
4092 * The hProcess member of the child process data structure will be change to the
4093 * harmless handle, while the hThread will be set to NULL.
4094 *
4095 * @param pThis The child process data structure.
4096 */
4097static void supR3HardNtChildCloseFullAccessHandles(PSUPR3HARDNTCHILD pThis)
4098{
4099 /*
4100 * The thread handle.
4101 */
4102 NTSTATUS rcNt = NtClose(pThis->hThread);
4103 if (!NT_SUCCESS(rcNt))
4104 supR3HardenedWinKillChild(pThis, "supR3HardenedWinReSpawn", rcNt, "NtClose(hThread) failed: %#x", rcNt);
4105 pThis->hThread = NULL;
4106
4107 /*
4108 * Duplicate the process handle into a harmless one.
4109 */
4110 HANDLE hProcWait;
4111 ULONG fRights = SYNCHRONIZE | PROCESS_TERMINATE | PROCESS_VM_READ;
4112 if (g_uNtVerCombined >= SUP_MAKE_NT_VER_SIMPLE(6, 0)) /* Introduced in Vista. */
4113 fRights |= PROCESS_QUERY_LIMITED_INFORMATION;
4114 else
4115 fRights |= PROCESS_QUERY_INFORMATION;
4116 rcNt = NtDuplicateObject(NtCurrentProcess(), pThis->hProcess,
4117 NtCurrentProcess(), &hProcWait,
4118 fRights, 0 /*HandleAttributes*/, 0);
4119 if (rcNt == STATUS_ACCESS_DENIED)
4120 {
4121 supR3HardenedError(rcNt, false /*fFatal*/,
4122 "supR3HardenedWinDoReSpawn: NtDuplicateObject(,,,,%#x,,) -> %#x, retrying with only %#x...\n",
4123 fRights, rcNt, SYNCHRONIZE);
4124 rcNt = NtDuplicateObject(NtCurrentProcess(), pThis->hProcess,
4125 NtCurrentProcess(), &hProcWait,
4126 SYNCHRONIZE, 0 /*HandleAttributes*/, 0);
4127 }
4128 if (!NT_SUCCESS(rcNt))
4129 supR3HardenedWinKillChild(pThis, "supR3HardenedWinReSpawn", rcNt,
4130 "NtDuplicateObject failed on child process handle: %#x\n", rcNt);
4131 /*
4132 * Close the process handle and replace it with the harmless one.
4133 */
4134 rcNt = NtClose(pThis->hProcess);
4135 pThis->hProcess = hProcWait;
4136 if (!NT_SUCCESS(rcNt))
4137 supR3HardenedWinKillChild(pThis, "supR3HardenedWinReSpawn", VERR_INVALID_NAME,
4138 "NtClose failed on child process handle: %#x\n", rcNt);
4139}
4140
4141
4142/**
4143 * This restores the child PEB and tweaks a couple of fields before we do the
4144 * child purification and let the process run normally.
4145 *
4146 * @param pThis The child process data structure.
4147 */
4148static void supR3HardNtChildSanitizePeb(PSUPR3HARDNTCHILD pThis)
4149{
4150 /*
4151 * Make a copy of the pre-execution PEB.
4152 */
4153 PEB Peb = pThis->Peb;
4154
4155#if 0
4156 /*
4157 * There should not be any activation context, so if there is, we scratch the memory associated with it.
4158 */
4159 int rc = 0;
4160 if (RT_SUCCESS(rc) && Peb.pShimData && !((uintptr_t)Peb.pShimData & PAGE_OFFSET_MASK))
4161 rc = supR3HardenedWinScratchChildMemory(hProcess, Peb.pShimData, PAGE_SIZE, "pShimData", pErrInfo);
4162 if (RT_SUCCESS(rc) && Peb.ActivationContextData && !((uintptr_t)Peb.ActivationContextData & PAGE_OFFSET_MASK))
4163 rc = supR3HardenedWinScratchChildMemory(hProcess, Peb.ActivationContextData, PAGE_SIZE, "ActivationContextData", pErrInfo);
4164 if (RT_SUCCESS(rc) && Peb.ProcessAssemblyStorageMap && !((uintptr_t)Peb.ProcessAssemblyStorageMap & PAGE_OFFSET_MASK))
4165 rc = supR3HardenedWinScratchChildMemory(hProcess, Peb.ProcessAssemblyStorageMap, PAGE_SIZE, "ProcessAssemblyStorageMap", pErrInfo);
4166 if (RT_SUCCESS(rc) && Peb.SystemDefaultActivationContextData && !((uintptr_t)Peb.SystemDefaultActivationContextData & PAGE_OFFSET_MASK))
4167 rc = supR3HardenedWinScratchChildMemory(hProcess, Peb.ProcessAssemblyStorageMap, PAGE_SIZE, "SystemDefaultActivationContextData", pErrInfo);
4168 if (RT_SUCCESS(rc) && Peb.SystemAssemblyStorageMap && !((uintptr_t)Peb.SystemAssemblyStorageMap & PAGE_OFFSET_MASK))
4169 rc = supR3HardenedWinScratchChildMemory(hProcess, Peb.SystemAssemblyStorageMap, PAGE_SIZE, "SystemAssemblyStorageMap", pErrInfo);
4170 if (RT_FAILURE(rc))
4171 return rc;
4172#endif
4173
4174 /*
4175 * Clear compatibility and activation related fields.
4176 */
4177 Peb.AppCompatFlags.QuadPart = 0;
4178 Peb.AppCompatFlagsUser.QuadPart = 0;
4179 Peb.pShimData = NULL;
4180 Peb.AppCompatInfo = NULL;
4181#if 0
4182 Peb.ActivationContextData = NULL;
4183 Peb.ProcessAssemblyStorageMap = NULL;
4184 Peb.SystemDefaultActivationContextData = NULL;
4185 Peb.SystemAssemblyStorageMap = NULL;
4186 /*Peb.Diff0.W6.IsProtectedProcess = 1;*/
4187#endif
4188
4189 /*
4190 * Write back the PEB.
4191 */
4192 SIZE_T cbActualMem = pThis->cbPeb;
4193 NTSTATUS rcNt = NtWriteVirtualMemory(pThis->hProcess, pThis->BasicInfo.PebBaseAddress, &Peb, pThis->cbPeb, &cbActualMem);
4194 if (!NT_SUCCESS(rcNt))
4195 supR3HardenedWinKillChild(pThis, "supR3HardNtChildSanitizePeb", rcNt,
4196 "NtWriteVirtualMemory/Peb failed: %#x", rcNt);
4197
4198}
4199
4200
4201/**
4202 * Purifies the child process after very early init has been performed.
4203 *
4204 * @param pThis The child process data structure.
4205 */
4206static void supR3HardNtChildPurify(PSUPR3HARDNTCHILD pThis)
4207{
4208 /*
4209 * We loop until we no longer make any fixes. This is similar to what
4210 * we do (or used to do, really) in the fAvastKludge case of
4211 * supR3HardenedWinInit. We might be up against asynchronous changes,
4212 * which we fudge by waiting a short while before earch purification. This
4213 * is arguably a fragile technique, but it's currently the best we've got.
4214 * Fortunately, most AVs seems to either favor immediate action on initial
4215 * load events or (much better for us) later events like kernel32.
4216 */
4217 uint64_t uMsTsOuterStart = supR3HardenedWinGetMilliTS();
4218 uint32_t cMsFudge = g_fSupAdversaries ? 512 : 256;
4219 uint32_t cTotalFixes = 0;
4220 uint32_t cFixes = 0; /* (MSC wrongly thinks this maybe used uninitialized) */
4221 for (uint32_t iLoop = 0; iLoop < 16; iLoop++)
4222 {
4223 /*
4224 * Delay.
4225 */
4226 uint32_t cSleeps = 0;
4227 uint64_t uMsTsStart = supR3HardenedWinGetMilliTS();
4228 do
4229 {
4230 NtYieldExecution();
4231 LARGE_INTEGER Time;
4232 Time.QuadPart = -8000000 / 100; /* 8ms in 100ns units, relative time. */
4233 NtDelayExecution(FALSE, &Time);
4234 cSleeps++;
4235 } while ( supR3HardenedWinGetMilliTS() - uMsTsStart <= cMsFudge
4236 || cSleeps < 8);
4237 SUP_DPRINTF(("supR3HardNtChildPurify: Startup delay kludge #1/%u: %u ms, %u sleeps\n",
4238 iLoop, supR3HardenedWinGetMilliTS() - uMsTsStart, cSleeps));
4239
4240 /*
4241 * Purify.
4242 */
4243 cFixes = 0;
4244 int rc = supHardenedWinVerifyProcess(pThis->hProcess, pThis->hThread, SUPHARDNTVPKIND_CHILD_PURIFICATION,
4245 g_fSupAdversaries & ( SUPHARDNT_ADVERSARY_TRENDMICRO_SAKFILE
4246 | SUPHARDNT_ADVERSARY_DIGITAL_GUARDIAN_OLD)
4247 ? SUPHARDNTVP_F_EXEC_ALLOC_REPLACE_WITH_RW : 0,
4248 &cFixes, RTErrInfoInitStatic(&g_ErrInfoStatic));
4249 if (RT_FAILURE(rc))
4250 supR3HardenedWinKillChild(pThis, "supR3HardNtChildPurify", rc,
4251 "supHardenedWinVerifyProcess failed with %Rrc: %s", rc, g_ErrInfoStatic.szMsg);
4252 if (cFixes == 0)
4253 {
4254 SUP_DPRINTF(("supR3HardNtChildPurify: Done after %llu ms and %u fixes (loop #%u).\n",
4255 supR3HardenedWinGetMilliTS() - uMsTsOuterStart, cTotalFixes, iLoop));
4256 return; /* We're probably good. */
4257 }
4258 cTotalFixes += cFixes;
4259
4260 if (!g_fSupAdversaries)
4261 g_fSupAdversaries |= SUPHARDNT_ADVERSARY_UNKNOWN;
4262 cMsFudge = 512;
4263
4264 /*
4265 * Log the KiOpPrefetchPatchCount value if available, hoping it might
4266 * sched some light on spider38's case.
4267 */
4268 ULONG cPatchCount = 0;
4269 NTSTATUS rcNt = NtQuerySystemInformation(SystemInformation_KiOpPrefetchPatchCount,
4270 &cPatchCount, sizeof(cPatchCount), NULL);
4271 if (NT_SUCCESS(rcNt))
4272 SUP_DPRINTF(("supR3HardNtChildPurify: cFixes=%u g_fSupAdversaries=%#x cPatchCount=%#u\n",
4273 cFixes, g_fSupAdversaries, cPatchCount));
4274 else
4275 SUP_DPRINTF(("supR3HardNtChildPurify: cFixes=%u g_fSupAdversaries=%#x\n", cFixes, g_fSupAdversaries));
4276 }
4277
4278 /*
4279 * We've given up fixing the child process. Probably fighting someone
4280 * that monitors their patches or/and our activities.
4281 */
4282 supR3HardenedWinKillChild(pThis, "supR3HardNtChildPurify", VERR_TRY_AGAIN,
4283 "Unable to purify child process! After 16 tries over %llu ms, we still %u fix(es) in the last pass.",
4284 supR3HardenedWinGetMilliTS() - uMsTsOuterStart, cFixes);
4285}
4286
4287
4288/**
4289 * Sets up the early process init.
4290 *
4291 * @param pThis The child process data structure.
4292 */
4293static void supR3HardNtChildSetUpChildInit(PSUPR3HARDNTCHILD pThis)
4294{
4295 uintptr_t const uChildExeAddr = (uintptr_t)pThis->Peb.ImageBaseAddress;
4296
4297 /*
4298 * Plant the process parameters. This ASSUMES the handle inheritance is
4299 * performed when creating the child process.
4300 */
4301 RT_ZERO(pThis->ProcParams);
4302 pThis->ProcParams.hEvtChild = pThis->hEvtChild;
4303 pThis->ProcParams.hEvtParent = pThis->hEvtParent;
4304 pThis->ProcParams.uNtDllAddr = pThis->uNtDllAddr;
4305 pThis->ProcParams.enmRequest = kSupR3WinChildReq_Error;
4306 pThis->ProcParams.rc = VINF_SUCCESS;
4307
4308 uintptr_t uChildAddr = uChildExeAddr + ((uintptr_t)&g_ProcParams - (uintptr_t)NtCurrentPeb()->ImageBaseAddress);
4309 SIZE_T cbIgnored;
4310 NTSTATUS rcNt = NtWriteVirtualMemory(pThis->hProcess, (PVOID)uChildAddr, &pThis->ProcParams,
4311 sizeof(pThis->ProcParams), &cbIgnored);
4312 if (!NT_SUCCESS(rcNt))
4313 supR3HardenedWinKillChild(pThis, "supR3HardenedWinSetupChildInit", rcNt,
4314 "NtWriteVirtualMemory(,%p,) failed writing child process parameters: %#x\n", uChildAddr, rcNt);
4315
4316 /*
4317 * Locate the LdrInitializeThunk address in the child as well as pristine
4318 * code bits for it.
4319 */
4320 PSUPHNTLDRCACHEENTRY pLdrEntry;
4321 int rc = supHardNtLdrCacheOpen("ntdll.dll", &pLdrEntry, NULL /*pErrInfo*/);
4322 if (RT_FAILURE(rc))
4323 supR3HardenedWinKillChild(pThis, "supR3HardenedWinSetupChildInit", rc,
4324 "supHardNtLdrCacheOpen failed on NTDLL: %Rrc\n", rc);
4325
4326 uint8_t *pbChildNtDllBits;
4327 rc = supHardNtLdrCacheEntryGetBits(pLdrEntry, &pbChildNtDllBits, pThis->uNtDllAddr, NULL, NULL, NULL /*pErrInfo*/);
4328 if (RT_FAILURE(rc))
4329 supR3HardenedWinKillChild(pThis, "supR3HardenedWinSetupChildInit", rc,
4330 "supHardNtLdrCacheEntryGetBits failed on NTDLL: %Rrc\n", rc);
4331
4332 RTLDRADDR uLdrInitThunk;
4333 rc = RTLdrGetSymbolEx(pLdrEntry->hLdrMod, pbChildNtDllBits, pThis->uNtDllAddr, UINT32_MAX,
4334 "LdrInitializeThunk", &uLdrInitThunk);
4335 if (RT_FAILURE(rc))
4336 supR3HardenedWinKillChild(pThis, "supR3HardenedWinSetupChildInit", rc,
4337 "Error locating LdrInitializeThunk in NTDLL: %Rrc", rc);
4338 PVOID pvLdrInitThunk = (PVOID)(uintptr_t)uLdrInitThunk;
4339 SUP_DPRINTF(("supR3HardenedWinSetupChildInit: uLdrInitThunk=%p\n", (uintptr_t)uLdrInitThunk));
4340
4341 /*
4342 * Calculate the address of our code in the child process.
4343 */
4344 uintptr_t uEarlyProcInitEP = uChildExeAddr + ( (uintptr_t)&supR3HardenedEarlyProcessInitThunk
4345 - (uintptr_t)NtCurrentPeb()->ImageBaseAddress);
4346
4347 /*
4348 * Compose the LdrInitializeThunk replacement bytes.
4349 * Note! The amount of code we replace here must be less or equal to what
4350 * the process verification code ignores.
4351 */
4352 uint8_t abNew[16];
4353 memcpy(abNew, pbChildNtDllBits + ((uintptr_t)uLdrInitThunk - pThis->uNtDllAddr), sizeof(abNew));
4354#ifdef RT_ARCH_AMD64
4355 abNew[0] = 0xff;
4356 abNew[1] = 0x25;
4357 *(uint32_t *)&abNew[2] = 0;
4358 *(uint64_t *)&abNew[6] = uEarlyProcInitEP;
4359#elif defined(RT_ARCH_X86)
4360 abNew[0] = 0xe9;
4361 *(uint32_t *)&abNew[1] = uEarlyProcInitEP - ((uint32_t)uLdrInitThunk + 5);
4362#else
4363# error "Unsupported arch."
4364#endif
4365
4366 /*
4367 * Install the LdrInitializeThunk replacement code in the child process.
4368 */
4369 PVOID pvProt = pvLdrInitThunk;
4370 SIZE_T cbProt = sizeof(abNew);
4371 ULONG fOldProt;
4372 rcNt = NtProtectVirtualMemory(pThis->hProcess, &pvProt, &cbProt, PAGE_EXECUTE_READWRITE, &fOldProt);
4373 if (!NT_SUCCESS(rcNt))
4374 supR3HardenedWinKillChild(pThis, "supR3HardenedWinSetupChildInit", rcNt,
4375 "NtProtectVirtualMemory/LdrInitializeThunk failed: %#x", rcNt);
4376
4377 rcNt = NtWriteVirtualMemory(pThis->hProcess, pvLdrInitThunk, abNew, sizeof(abNew), &cbIgnored);
4378 if (!NT_SUCCESS(rcNt))
4379 supR3HardenedWinKillChild(pThis, "supR3HardenedWinSetupChildInit", rcNt,
4380 "NtWriteVirtualMemory/LdrInitializeThunk failed: %#x", rcNt);
4381
4382 pvProt = pvLdrInitThunk;
4383 cbProt = sizeof(abNew);
4384 rcNt = NtProtectVirtualMemory(pThis->hProcess, &pvProt, &cbProt, fOldProt, &fOldProt);
4385 if (!NT_SUCCESS(rcNt))
4386 supR3HardenedWinKillChild(pThis, "supR3HardenedWinSetupChildInit", rcNt,
4387 "NtProtectVirtualMemory/LdrInitializeThunk[restore] failed: %#x", rcNt);
4388
4389 /*
4390 * Check the sanity of the thread context.
4391 */
4392 CONTEXT Ctx;
4393 RT_ZERO(Ctx);
4394 Ctx.ContextFlags = CONTEXT_FULL | CONTEXT_DEBUG_REGISTERS;
4395 rcNt = NtGetContextThread(pThis->hThread, &Ctx);
4396 if (NT_SUCCESS(rcNt))
4397 {
4398#ifdef RT_ARCH_AMD64
4399 DWORD64 *pPC = &Ctx.Rip;
4400#elif defined(RT_ARCH_X86)
4401 DWORD *pPC = &Ctx.Eip;
4402#else
4403# error "Unsupported arch."
4404#endif
4405 supR3HardNtDprintCtx(&Ctx, "supR3HardenedWinSetupChildInit: Initial context:");
4406
4407 /* Entrypoint for the executable: */
4408 uintptr_t const uChildMain = uChildExeAddr + ( (uintptr_t)&suplibHardenedWindowsMain
4409 - (uintptr_t)NtCurrentPeb()->ImageBaseAddress);
4410
4411 /* NtDll size and the more recent default thread start entrypoint (Vista+?): */
4412 RTLDRADDR uSystemThreadStart;
4413 rc = RTLdrGetSymbolEx(pLdrEntry->hLdrMod, pbChildNtDllBits, pThis->uNtDllAddr, UINT32_MAX,
4414 "RtlUserThreadStart", &uSystemThreadStart);
4415 if (RT_FAILURE(rc))
4416 uSystemThreadStart = 0;
4417
4418 /* Kernel32 for thread start of older windows version, only XP64/W2K3-64 has an actual
4419 export for it. Unfortunately, it is not yet loaded into the child, so we have to
4420 assume same location as in the parent (safe): */
4421 PSUPHNTLDRCACHEENTRY pLdrEntryKernel32;
4422 rc = supHardNtLdrCacheOpen("kernel32.dll", &pLdrEntryKernel32, NULL /*pErrInfo*/);
4423 if (RT_FAILURE(rc))
4424 supR3HardenedWinKillChild(pThis, "supR3HardenedWinSetupChildInit", rc,
4425 "supHardNtLdrCacheOpen failed on KERNEL32: %Rrc\n", rc);
4426 size_t const cbKernel32 = RTLdrSize(pLdrEntryKernel32->hLdrMod);
4427
4428#ifdef RT_ARCH_AMD64
4429 if (!uSystemThreadStart)
4430 {
4431 rc = RTLdrGetSymbolEx(pLdrEntry->hLdrMod, pbChildNtDllBits, pLdrEntryKernel32->uImageBase, UINT32_MAX,
4432 "BaseProcessStart", &uSystemThreadStart);
4433 if (RT_FAILURE(rc))
4434 uSystemThreadStart = 0;
4435 }
4436#endif
4437
4438 bool fUpdateContext = false;
4439
4440 /* Check if the RIP looks half sane, try correct it if it isn't.
4441 It should point to RtlUserThreadStart (Vista and later it seem), though only
4442 tested on win10. The first parameter is the executable entrypoint, the 2nd
4443 is probably the PEB. Before Vista it should point to Kernel32!BaseProcessStart,
4444 though the symbol is only exported in 5.2/AMD64. */
4445 if ( ( uSystemThreadStart
4446 ? *pPC == uSystemThreadStart
4447 : *pPC - ( pLdrEntryKernel32->uImageBase != ~(uintptr_t)0 ? pLdrEntryKernel32->uImageBase
4448 : (uintptr_t)GetModuleHandleW(L"kernel32.dll")) <= cbKernel32)
4449 || *pPC == uChildMain)
4450 { }
4451 else
4452 {
4453 SUP_DPRINTF(("Warning! Bogus RIP: %p (uSystemThreadStart=%p; kernel32 %p LB %p; uChildMain=%p)\n",
4454 *pPC, uSystemThreadStart, pLdrEntryKernel32->uImageBase, cbKernel32, uChildMain));
4455 if (uSystemThreadStart)
4456 {
4457 SUP_DPRINTF(("Correcting RIP from to %p hoping that it might work...\n", (uintptr_t)uSystemThreadStart));
4458 *pPC = uSystemThreadStart;
4459 fUpdateContext = true;
4460 }
4461 }
4462#ifdef RT_ARCH_AMD64
4463 if (g_uNtVerCombined >= SUP_MAKE_NT_VER_SIMPLE(10, 0)) /* W2K3: CS=33 SS=DS=ES=GS=2b FS=53 */
4464 {
4465 if (Ctx.SegDs != 0)
4466 SUP_DPRINTF(("Warning! Bogus DS: %04x, expected zero\n", Ctx.SegDs));
4467 if (Ctx.SegEs != 0)
4468 SUP_DPRINTF(("Warning! Bogus ES: %04x, expected zero\n", Ctx.SegEs));
4469 if (Ctx.SegFs != 0)
4470 SUP_DPRINTF(("Warning! Bogus FS: %04x, expected zero\n", Ctx.SegFs));
4471 if (Ctx.SegGs != 0)
4472 SUP_DPRINTF(("Warning! Bogus GS: %04x, expected zero\n", Ctx.SegGs));
4473 }
4474 if (Ctx.Rcx != uChildMain)
4475 SUP_DPRINTF(("Warning! Bogus RCX: %016RX64, expected %016RX64\n", Ctx.Rcx, uChildMain));
4476 if (Ctx.Rdx & PAGE_OFFSET_MASK)
4477 SUP_DPRINTF(("Warning! Bogus RDX: %016RX64, expected page aligned\n", Ctx.Rdx)); /* PEB */
4478 if ((Ctx.Rsp & 15) != 8)
4479 SUP_DPRINTF(("Warning! Misaligned RSP: %016RX64\n", Ctx.Rsp));
4480#endif
4481 if (Ctx.SegCs != ASMGetCS())
4482 SUP_DPRINTF(("Warning! Bogus CS: %04x, expected %04x\n", Ctx.SegCs, ASMGetCS()));
4483 if (Ctx.SegSs != ASMGetSS())
4484 SUP_DPRINTF(("Warning! Bogus SS: %04x, expected %04x\n", Ctx.SegSs, ASMGetSS()));
4485 if (Ctx.Dr0 != 0)
4486 SUP_DPRINTF(("Warning! Bogus DR0: %016RX64, expected zero\n", Ctx.Dr0));
4487 if (Ctx.Dr1 != 0)
4488 SUP_DPRINTF(("Warning! Bogus DR1: %016RX64, expected zero\n", Ctx.Dr1));
4489 if (Ctx.Dr2 != 0)
4490 SUP_DPRINTF(("Warning! Bogus DR2: %016RX64, expected zero\n", Ctx.Dr2));
4491 if (Ctx.Dr3 != 0)
4492 SUP_DPRINTF(("Warning! Bogus DR3: %016RX64, expected zero\n", Ctx.Dr3));
4493 if (Ctx.Dr6 != 0)
4494 SUP_DPRINTF(("Warning! Bogus DR6: %016RX64, expected zero\n", Ctx.Dr6));
4495 if (Ctx.Dr7 != 0)
4496 {
4497 SUP_DPRINTF(("Warning! Bogus DR7: %016RX64, expected zero\n", Ctx.Dr7));
4498 Ctx.Dr7 = 0;
4499 fUpdateContext = true;
4500 }
4501
4502 if (fUpdateContext)
4503 {
4504 rcNt = NtSetContextThread(pThis->hThread, &Ctx);
4505 if (!NT_SUCCESS(rcNt))
4506 SUP_DPRINTF(("Error! NtSetContextThread failed: %#x\n", rcNt));
4507 }
4508 }
4509
4510 /* Caller starts child execution. */
4511 SUP_DPRINTF(("supR3HardenedWinSetupChildInit: Start child.\n"));
4512}
4513
4514
4515
4516/**
4517 * This messes with the child PEB before we trigger the initial image events.
4518 *
4519 * @param pThis The child process data structure.
4520 */
4521static void supR3HardNtChildScrewUpPebForInitialImageEvents(PSUPR3HARDNTCHILD pThis)
4522{
4523 /*
4524 * Not sure if any of the cracker software uses the PEB at this point, but
4525 * just in case they do make some of the PEB fields a little less useful.
4526 */
4527 PEB Peb = pThis->Peb;
4528
4529 /* Make ImageBaseAddress useless. */
4530 Peb.ImageBaseAddress = (PVOID)((uintptr_t)Peb.ImageBaseAddress ^ UINT32_C(0x5f139000));
4531#ifdef RT_ARCH_AMD64
4532 Peb.ImageBaseAddress = (PVOID)((uintptr_t)Peb.ImageBaseAddress | UINT64_C(0x0313000000000000));
4533#endif
4534
4535 /*
4536 * Write the PEB.
4537 */
4538 SIZE_T cbActualMem = pThis->cbPeb;
4539 NTSTATUS rcNt = NtWriteVirtualMemory(pThis->hProcess, pThis->BasicInfo.PebBaseAddress, &Peb, pThis->cbPeb, &cbActualMem);
4540 if (!NT_SUCCESS(rcNt))
4541 supR3HardenedWinKillChild(pThis, "supR3HardNtChildScrewUpPebForInitialImageEvents", rcNt,
4542 "NtWriteVirtualMemory/Peb failed: %#x", rcNt);
4543}
4544
4545
4546/**
4547 * Check if the zero terminated NT unicode string is the path to the given
4548 * system32 DLL.
4549 *
4550 * @returns true if it is, false if not.
4551 * @param pUniStr The zero terminated NT unicode string path.
4552 * @param pszName The name of the system32 DLL.
4553 */
4554static bool supR3HardNtIsNamedSystem32Dll(PUNICODE_STRING pUniStr, const char *pszName)
4555{
4556 if (pUniStr->Length > g_System32NtPath.UniStr.Length)
4557 {
4558 if (memcmp(pUniStr->Buffer, g_System32NtPath.UniStr.Buffer, g_System32NtPath.UniStr.Length) == 0)
4559 {
4560 if (pUniStr->Buffer[g_System32NtPath.UniStr.Length / sizeof(WCHAR)] == '\\')
4561 {
4562 if (RTUtf16ICmpAscii(&pUniStr->Buffer[g_System32NtPath.UniStr.Length / sizeof(WCHAR) + 1], pszName) == 0)
4563 return true;
4564 }
4565 }
4566 }
4567
4568 return false;
4569}
4570
4571
4572/**
4573 * Worker for supR3HardNtChildGatherData that locates NTDLL in the child
4574 * process.
4575 *
4576 * @param pThis The child process data structure.
4577 */
4578static void supR3HardNtChildFindNtdll(PSUPR3HARDNTCHILD pThis)
4579{
4580 /*
4581 * Find NTDLL in this process first and take that as a starting point.
4582 */
4583 pThis->uNtDllParentAddr = (uintptr_t)GetModuleHandleW(L"ntdll.dll");
4584 SUPR3HARDENED_ASSERT(pThis->uNtDllParentAddr != 0 && !(pThis->uNtDllParentAddr & PAGE_OFFSET_MASK));
4585 pThis->uNtDllAddr = pThis->uNtDllParentAddr;
4586
4587 /*
4588 * Scan the virtual memory of the child.
4589 */
4590 uintptr_t cbAdvance = 0;
4591 uintptr_t uPtrWhere = 0;
4592 for (uint32_t i = 0; i < 1024; i++)
4593 {
4594 /* Query information. */
4595 SIZE_T cbActual = 0;
4596 MEMORY_BASIC_INFORMATION MemInfo = { 0, 0, 0, 0, 0, 0, 0 };
4597 NTSTATUS rcNt = NtQueryVirtualMemory(pThis->hProcess,
4598 (void const *)uPtrWhere,
4599 MemoryBasicInformation,
4600 &MemInfo,
4601 sizeof(MemInfo),
4602 &cbActual);
4603 if (!NT_SUCCESS(rcNt))
4604 break;
4605
4606 if ( MemInfo.Type == SEC_IMAGE
4607 || MemInfo.Type == SEC_PROTECTED_IMAGE
4608 || MemInfo.Type == (SEC_IMAGE | SEC_PROTECTED_IMAGE))
4609 {
4610 if (MemInfo.BaseAddress == MemInfo.AllocationBase)
4611 {
4612 /* Get the image name. */
4613 union
4614 {
4615 UNICODE_STRING UniStr;
4616 uint8_t abPadding[4096];
4617 } uBuf;
4618 rcNt = NtQueryVirtualMemory(pThis->hProcess,
4619 MemInfo.BaseAddress,
4620 MemorySectionName,
4621 &uBuf,
4622 sizeof(uBuf) - sizeof(WCHAR),
4623 &cbActual);
4624 if (NT_SUCCESS(rcNt))
4625 {
4626 uBuf.UniStr.Buffer[uBuf.UniStr.Length / sizeof(WCHAR)] = '\0';
4627 if (supR3HardNtIsNamedSystem32Dll(&uBuf.UniStr, "ntdll.dll"))
4628 {
4629 pThis->uNtDllAddr = (uintptr_t)MemInfo.AllocationBase;
4630 SUP_DPRINTF(("supR3HardNtPuChFindNtdll: uNtDllParentAddr=%p uNtDllChildAddr=%p\n",
4631 pThis->uNtDllParentAddr, pThis->uNtDllAddr));
4632 return;
4633 }
4634 }
4635 }
4636 }
4637
4638 /*
4639 * Advance.
4640 */
4641 cbAdvance = MemInfo.RegionSize;
4642 if (uPtrWhere + cbAdvance <= uPtrWhere)
4643 break;
4644 uPtrWhere += MemInfo.RegionSize;
4645 }
4646
4647 supR3HardenedWinKillChild(pThis, "supR3HardNtChildFindNtdll", VERR_MODULE_NOT_FOUND, "ntdll.dll not found in child process.");
4648}
4649
4650
4651/**
4652 * Gather child data.
4653 *
4654 * @param pThis The child process data structure.
4655 */
4656static void supR3HardNtChildGatherData(PSUPR3HARDNTCHILD pThis)
4657{
4658 /*
4659 * Basic info.
4660 */
4661 ULONG cbActual = 0;
4662 NTSTATUS rcNt = NtQueryInformationProcess(pThis->hProcess, ProcessBasicInformation,
4663 &pThis->BasicInfo, sizeof(pThis->BasicInfo), &cbActual);
4664 if (!NT_SUCCESS(rcNt))
4665 supR3HardenedWinKillChild(pThis, "supR3HardNtChildGatherData", rcNt,
4666 "NtQueryInformationProcess/ProcessBasicInformation failed: %#x", rcNt);
4667
4668 /*
4669 * If this is the middle (stub) process, we wish to wait for both child
4670 * and parent. So open the parent process. Not fatal if we cannnot.
4671 */
4672 if (pThis->iWhich > 1)
4673 {
4674 PROCESS_BASIC_INFORMATION SelfInfo;
4675 rcNt = NtQueryInformationProcess(NtCurrentProcess(), ProcessBasicInformation, &SelfInfo, sizeof(SelfInfo), &cbActual);
4676 if (NT_SUCCESS(rcNt))
4677 {
4678 OBJECT_ATTRIBUTES ObjAttr;
4679 InitializeObjectAttributes(&ObjAttr, NULL, 0, NULL /*hRootDir*/, NULL /*pSecDesc*/);
4680
4681 CLIENT_ID ClientId;
4682 ClientId.UniqueProcess = (HANDLE)SelfInfo.InheritedFromUniqueProcessId;
4683 ClientId.UniqueThread = NULL;
4684
4685 rcNt = NtOpenProcess(&pThis->hParent, SYNCHRONIZE | PROCESS_QUERY_INFORMATION, &ObjAttr, &ClientId);
4686#ifdef DEBUG
4687 SUPR3HARDENED_ASSERT_NT_SUCCESS(rcNt);
4688#endif
4689 if (!NT_SUCCESS(rcNt))
4690 {
4691 pThis->hParent = NULL;
4692 SUP_DPRINTF(("supR3HardNtChildGatherData: Failed to open parent process (%#p): %#x\n", ClientId.UniqueProcess, rcNt));
4693 }
4694 }
4695
4696 }
4697
4698 /*
4699 * Process environment block.
4700 */
4701 if (g_uNtVerCombined < SUP_NT_VER_W2K3)
4702 pThis->cbPeb = PEB_SIZE_W51;
4703 else if (g_uNtVerCombined < SUP_NT_VER_VISTA)
4704 pThis->cbPeb = PEB_SIZE_W52;
4705 else if (g_uNtVerCombined < SUP_NT_VER_W70)
4706 pThis->cbPeb = PEB_SIZE_W6;
4707 else if (g_uNtVerCombined < SUP_NT_VER_W80)
4708 pThis->cbPeb = PEB_SIZE_W7;
4709 else if (g_uNtVerCombined < SUP_NT_VER_W81)
4710 pThis->cbPeb = PEB_SIZE_W80;
4711 else
4712 pThis->cbPeb = PEB_SIZE_W81;
4713
4714 SUP_DPRINTF(("supR3HardNtChildGatherData: PebBaseAddress=%p cbPeb=%#x\n",
4715 pThis->BasicInfo.PebBaseAddress, pThis->cbPeb));
4716
4717 SIZE_T cbActualMem;
4718 RT_ZERO(pThis->Peb);
4719 rcNt = NtReadVirtualMemory(pThis->hProcess, pThis->BasicInfo.PebBaseAddress, &pThis->Peb, sizeof(pThis->Peb), &cbActualMem);
4720 if (!NT_SUCCESS(rcNt))
4721 supR3HardenedWinKillChild(pThis, "supR3HardNtChildGatherData", rcNt,
4722 "NtReadVirtualMemory/Peb failed: %#x", rcNt);
4723
4724 /*
4725 * Locate NtDll.
4726 */
4727 supR3HardNtChildFindNtdll(pThis);
4728}
4729
4730
4731/**
4732 * Does the actually respawning.
4733 *
4734 * @returns Never, will call exit or raise fatal error.
4735 * @param iWhich Which respawn we're to check for, 1 being the
4736 * first one, and 2 the second and final.
4737 */
4738static DECL_NO_RETURN(void) supR3HardenedWinDoReSpawn(int iWhich)
4739{
4740 NTSTATUS rcNt;
4741 PPEB pPeb = NtCurrentPeb();
4742 PRTL_USER_PROCESS_PARAMETERS pParentProcParams = pPeb->ProcessParameters;
4743
4744 SUPR3HARDENED_ASSERT(g_cSuplibHardenedWindowsMainCalls == 1);
4745
4746 /*
4747 * Init the child process data structure, creating the child communication
4748 * event sempahores.
4749 */
4750 SUPR3HARDNTCHILD This;
4751 RT_ZERO(This);
4752 This.iWhich = iWhich;
4753
4754 OBJECT_ATTRIBUTES ObjAttrs;
4755 This.hEvtChild = NULL;
4756 InitializeObjectAttributes(&ObjAttrs, NULL /*pName*/, OBJ_INHERIT, NULL /*hRootDir*/, NULL /*pSecDesc*/);
4757 SUPR3HARDENED_ASSERT_NT_SUCCESS(NtCreateEvent(&This.hEvtChild, EVENT_ALL_ACCESS, &ObjAttrs, SynchronizationEvent, FALSE));
4758
4759 This.hEvtParent = NULL;
4760 InitializeObjectAttributes(&ObjAttrs, NULL /*pName*/, OBJ_INHERIT, NULL /*hRootDir*/, NULL /*pSecDesc*/);
4761 SUPR3HARDENED_ASSERT_NT_SUCCESS(NtCreateEvent(&This.hEvtParent, EVENT_ALL_ACCESS, &ObjAttrs, SynchronizationEvent, FALSE));
4762
4763 /*
4764 * Set up security descriptors.
4765 */
4766 SECURITY_ATTRIBUTES ProcessSecAttrs;
4767 MYSECURITYCLEANUP ProcessSecAttrsCleanup;
4768 supR3HardNtChildInitSecAttrs(&ProcessSecAttrs, &ProcessSecAttrsCleanup, true /*fProcess*/);
4769
4770 SECURITY_ATTRIBUTES ThreadSecAttrs;
4771 MYSECURITYCLEANUP ThreadSecAttrsCleanup;
4772 supR3HardNtChildInitSecAttrs(&ThreadSecAttrs, &ThreadSecAttrsCleanup, false /*fProcess*/);
4773
4774#if 1
4775 /*
4776 * Configure the startup info and creation flags.
4777 */
4778 DWORD dwCreationFlags = CREATE_SUSPENDED;
4779
4780 STARTUPINFOEXW SiEx;
4781 suplibHardenedMemSet(&SiEx, 0, sizeof(SiEx));
4782 if (1)
4783 SiEx.StartupInfo.cb = sizeof(SiEx.StartupInfo);
4784 else
4785 {
4786 SiEx.StartupInfo.cb = sizeof(SiEx);
4787 dwCreationFlags |= EXTENDED_STARTUPINFO_PRESENT;
4788 /** @todo experiment with protected process stuff later on. */
4789 }
4790
4791 SiEx.StartupInfo.dwFlags |= pParentProcParams->WindowFlags & STARTF_USESHOWWINDOW;
4792 SiEx.StartupInfo.wShowWindow = (WORD)pParentProcParams->ShowWindowFlags;
4793
4794 SiEx.StartupInfo.dwFlags |= STARTF_USESTDHANDLES;
4795 SiEx.StartupInfo.hStdInput = pParentProcParams->StandardInput;
4796 SiEx.StartupInfo.hStdOutput = pParentProcParams->StandardOutput;
4797 SiEx.StartupInfo.hStdError = pParentProcParams->StandardError;
4798
4799 /*
4800 * Construct the command line and launch the process.
4801 */
4802 PRTUTF16 pwszCmdLine = supR3HardNtChildConstructCmdLine(NULL, iWhich);
4803
4804 supR3HardenedWinEnableThreadCreation();
4805 PROCESS_INFORMATION ProcessInfoW32 = { NULL, NULL, 0, 0 };
4806 if (!CreateProcessW(g_wszSupLibHardenedExePath,
4807 pwszCmdLine,
4808 &ProcessSecAttrs,
4809 &ThreadSecAttrs,
4810 TRUE /*fInheritHandles*/,
4811 dwCreationFlags,
4812 NULL /*pwszzEnvironment*/,
4813 NULL /*pwszCurDir*/,
4814 &SiEx.StartupInfo,
4815 &ProcessInfoW32))
4816 supR3HardenedFatalMsg("supR3HardenedWinReSpawn", kSupInitOp_Misc, VERR_INVALID_NAME,
4817 "Error relaunching VirtualBox VM process: %u\n"
4818 "Command line: '%ls'",
4819 RtlGetLastWin32Error(), pwszCmdLine);
4820 supR3HardenedWinDisableThreadCreation();
4821
4822 SUP_DPRINTF(("supR3HardenedWinDoReSpawn(%d): New child %x.%x [kernel32].\n",
4823 iWhich, ProcessInfoW32.dwProcessId, ProcessInfoW32.dwThreadId));
4824 This.hProcess = ProcessInfoW32.hProcess;
4825 This.hThread = ProcessInfoW32.hThread;
4826
4827#else
4828
4829 /*
4830 * Construct the process parameters.
4831 */
4832 UNICODE_STRING W32ImageName;
4833 W32ImageName.Buffer = g_wszSupLibHardenedExePath; /* Yes the windows name for the process parameters. */
4834 W32ImageName.Length = (USHORT)RTUtf16Len(g_wszSupLibHardenedExePath) * sizeof(WCHAR);
4835 W32ImageName.MaximumLength = W32ImageName.Length + sizeof(WCHAR);
4836
4837 UNICODE_STRING CmdLine;
4838 supR3HardNtChildConstructCmdLine(&CmdLine, iWhich);
4839
4840 PRTL_USER_PROCESS_PARAMETERS pProcParams = NULL;
4841 SUPR3HARDENED_ASSERT_NT_SUCCESS(RtlCreateProcessParameters(&pProcParams,
4842 &W32ImageName,
4843 NULL /* DllPath - inherit from this process */,
4844 NULL /* CurrentDirectory - inherit from this process */,
4845 &CmdLine,
4846 NULL /* Environment - inherit from this process */,
4847 NULL /* WindowsTitle - none */,
4848 NULL /* DesktopTitle - none. */,
4849 NULL /* ShellInfo - none. */,
4850 NULL /* RuntimeInfo - none (byte array for MSVCRT file info) */)
4851 );
4852
4853 /** @todo this doesn't work. :-( */
4854 pProcParams->ConsoleHandle = pParentProcParams->ConsoleHandle;
4855 pProcParams->ConsoleFlags = pParentProcParams->ConsoleFlags;
4856 pProcParams->StandardInput = pParentProcParams->StandardInput;
4857 pProcParams->StandardOutput = pParentProcParams->StandardOutput;
4858 pProcParams->StandardError = pParentProcParams->StandardError;
4859
4860 RTL_USER_PROCESS_INFORMATION ProcessInfoNt = { sizeof(ProcessInfoNt) };
4861 rcNt = RtlCreateUserProcess(&g_SupLibHardenedExeNtPath.UniStr,
4862 OBJ_INHERIT | OBJ_CASE_INSENSITIVE /*Attributes*/,
4863 pProcParams,
4864 NULL, //&ProcessSecAttrs,
4865 NULL, //&ThreadSecAttrs,
4866 NtCurrentProcess() /* ParentProcess */,
4867 FALSE /*fInheritHandles*/,
4868 NULL /* DebugPort */,
4869 NULL /* ExceptionPort */,
4870 &ProcessInfoNt);
4871 if (!NT_SUCCESS(rcNt))
4872 supR3HardenedFatalMsg("supR3HardenedWinReSpawn", kSupInitOp_Misc, VERR_INVALID_NAME,
4873 "Error relaunching VirtualBox VM process: %#x\n"
4874 "Command line: '%ls'",
4875 rcNt, CmdLine.Buffer);
4876
4877 SUP_DPRINTF(("supR3HardenedWinDoReSpawn(%d): New child %x.%x [ntdll].\n",
4878 iWhich, ProcessInfo.ClientId.UniqueProcess, ProcessInfo.ClientId.UniqueThread));
4879 RtlDestroyProcessParameters(pProcParams);
4880
4881 This.hProcess = ProcessInfoNt.ProcessHandle;
4882 This.hThread = ProcessInfoNt.ThreadHandle;
4883#endif
4884
4885#ifndef VBOX_WITHOUT_DEBUGGER_CHECKS
4886 /*
4887 * Apply anti debugger notification trick to the thread. (Also done in
4888 * supR3HardenedWinInit.) This may fail with STATUS_ACCESS_DENIED and
4889 * maybe other errors. (Unfortunately, recent (SEP 12.1) of symantec's
4890 * sysplant.sys driver will cause process deadlocks and a shutdown/reboot
4891 * denial of service problem if we hide the initial thread, so we postpone
4892 * this action if we've detected SEP.)
4893 */
4894 if (!(g_fSupAdversaries & (SUPHARDNT_ADVERSARY_SYMANTEC_SYSPLANT | SUPHARDNT_ADVERSARY_SYMANTEC_N360)))
4895 {
4896 rcNt = NtSetInformationThread(This.hThread, ThreadHideFromDebugger, NULL, 0);
4897 if (!NT_SUCCESS(rcNt))
4898 SUP_DPRINTF(("supR3HardenedWinReSpawn: NtSetInformationThread/ThreadHideFromDebugger failed: %#x (harmless)\n", rcNt));
4899 }
4900#endif
4901
4902 /*
4903 * Perform very early child initialization.
4904 */
4905 supR3HardNtChildGatherData(&This);
4906 supR3HardNtChildScrewUpPebForInitialImageEvents(&This);
4907 supR3HardNtChildSetUpChildInit(&This);
4908
4909 ULONG cSuspendCount = 0;
4910 rcNt = NtResumeThread(This.hThread, &cSuspendCount);
4911 if (!NT_SUCCESS(rcNt))
4912 supR3HardenedWinKillChild(&This, "supR3HardenedWinDoReSpawn", rcNt, "NtResumeThread failed: %#x", rcNt);
4913
4914 /*
4915 * Santizie the pre-NTDLL child when it's ready.
4916 *
4917 * AV software and other things injecting themselves into the embryonic
4918 * and budding process to intercept API calls and what not. Unfortunately
4919 * this is also the behavior of viruses, malware and other unfriendly
4920 * software, so we won't stand for it. AV software can scan our image
4921 * as they are loaded via kernel hooks, that's sufficient. No need for
4922 * patching half of NTDLL or messing with the import table of the
4923 * process executable.
4924 */
4925 supR3HardNtChildWaitFor(&This, kSupR3WinChildReq_PurifyChildAndCloseHandles, 2000 /*ms*/, "PurifyChildAndCloseHandles");
4926 supR3HardNtChildPurify(&This);
4927 supR3HardNtChildSanitizePeb(&This);
4928
4929 /*
4930 * Close the unrestricted access handles. Since we need to wait on the
4931 * child process, we'll reopen the process with limited access before doing
4932 * away with the process handle returned by CreateProcess.
4933 */
4934 supR3HardNtChildCloseFullAccessHandles(&This);
4935
4936 /*
4937 * Signal the child that we've closed the unrestricted handles and it can
4938 * safely try open the driver.
4939 */
4940 rcNt = NtSetEvent(This.hEvtChild, NULL);
4941 if (!NT_SUCCESS(rcNt))
4942 supR3HardenedWinKillChild(&This, "supR3HardenedWinReSpawn", VERR_INVALID_NAME,
4943 "NtSetEvent failed on child process handle: %#x\n", rcNt);
4944
4945 /*
4946 * Ditch the loader cache so we don't sit on too much memory while waiting.
4947 */
4948 supR3HardenedWinFlushLoaderCache();
4949 supR3HardenedWinCompactHeaps();
4950
4951 /*
4952 * Enable thread creation at this point so Ctrl-C and Ctrl-Break can be processed.
4953 */
4954 supR3HardenedWinEnableThreadCreation();
4955
4956 /*
4957 * Wait for the child to get to suplibHardenedWindowsMain so we can close the handles.
4958 */
4959 supR3HardNtChildWaitFor(&This, kSupR3WinChildReq_CloseEvents, 60000 /*ms*/, "CloseEvents");
4960
4961 NtClose(This.hEvtChild);
4962 NtClose(This.hEvtParent);
4963 This.hEvtChild = NULL;
4964 This.hEvtParent = NULL;
4965
4966 /*
4967 * Wait for the process to terminate.
4968 */
4969 supR3HardNtChildWaitFor(&This, kSupR3WinChildReq_End, RT_INDEFINITE_WAIT, "the end");
4970 supR3HardenedFatal("supR3HardenedWinDoReSpawn: supR3HardNtChildWaitFor unexpectedly returned!\n");
4971 /* not reached*/
4972}
4973
4974
4975/**
4976 * Logs the content of the given object directory.
4977 *
4978 * @returns true if it exists, false if not.
4979 * @param pszDir The path of the directory to log (ASCII).
4980 */
4981static void supR3HardenedWinLogObjDir(const char *pszDir)
4982{
4983 /*
4984 * Open the driver object directory.
4985 */
4986 RTUTF16 wszDir[128];
4987 int rc = RTUtf16CopyAscii(wszDir, RT_ELEMENTS(wszDir), pszDir);
4988 if (RT_FAILURE(rc))
4989 {
4990 SUP_DPRINTF(("supR3HardenedWinLogObjDir: RTUtf16CopyAscii -> %Rrc on '%s'\n", rc, pszDir));
4991 return;
4992 }
4993
4994 UNICODE_STRING NtDirName;
4995 NtDirName.Buffer = (WCHAR *)wszDir;
4996 NtDirName.Length = (USHORT)(RTUtf16Len(wszDir) * sizeof(WCHAR));
4997 NtDirName.MaximumLength = NtDirName.Length + sizeof(WCHAR);
4998
4999 OBJECT_ATTRIBUTES ObjAttr;
5000 InitializeObjectAttributes(&ObjAttr, &NtDirName, OBJ_CASE_INSENSITIVE, NULL /*hRootDir*/, NULL /*pSecDesc*/);
5001
5002 HANDLE hDir;
5003 NTSTATUS rcNt = NtOpenDirectoryObject(&hDir, DIRECTORY_QUERY | FILE_LIST_DIRECTORY, &ObjAttr);
5004 SUP_DPRINTF(("supR3HardenedWinLogObjDir: %ls => %#x\n", wszDir, rcNt));
5005 if (!NT_SUCCESS(rcNt))
5006 return;
5007
5008 /*
5009 * Enumerate it, looking for the driver.
5010 */
5011 ULONG uObjDirCtx = 0;
5012 for (;;)
5013 {
5014 uint32_t abBuffer[_64K + _1K];
5015 ULONG cbActual;
5016 rcNt = NtQueryDirectoryObject(hDir,
5017 abBuffer,
5018 sizeof(abBuffer) - 4, /* minus four for string terminator space. */
5019 FALSE /*ReturnSingleEntry */,
5020 FALSE /*RestartScan*/,
5021 &uObjDirCtx,
5022 &cbActual);
5023 if (!NT_SUCCESS(rcNt) || cbActual < sizeof(OBJECT_DIRECTORY_INFORMATION))
5024 {
5025 SUP_DPRINTF(("supR3HardenedWinLogObjDir: NtQueryDirectoryObject => rcNt=%#x cbActual=%#x\n", rcNt, cbActual));
5026 break;
5027 }
5028
5029 POBJECT_DIRECTORY_INFORMATION pObjDir = (POBJECT_DIRECTORY_INFORMATION)abBuffer;
5030 while (pObjDir->Name.Length != 0)
5031 {
5032 SUP_DPRINTF((" %.*ls %.*ls\n",
5033 pObjDir->TypeName.Length / sizeof(WCHAR), pObjDir->TypeName.Buffer,
5034 pObjDir->Name.Length / sizeof(WCHAR), pObjDir->Name.Buffer));
5035
5036 /* Next directory entry. */
5037 pObjDir++;
5038 }
5039 }
5040
5041 /*
5042 * Clean up and return.
5043 */
5044 NtClose(hDir);
5045}
5046
5047
5048/**
5049 * Tries to open VBoxDrvErrorInfo and read extra error info from it.
5050 *
5051 * @returns pszErrorInfo.
5052 * @param pszErrorInfo The destination buffer. Will always be
5053 * terminated.
5054 * @param cbErrorInfo The size of the destination buffer.
5055 * @param pszPrefix What to prefix the error info with, if we got
5056 * anything.
5057 */
5058DECLHIDDEN(char *) supR3HardenedWinReadErrorInfoDevice(char *pszErrorInfo, size_t cbErrorInfo, const char *pszPrefix)
5059{
5060 RT_BZERO(pszErrorInfo, cbErrorInfo);
5061
5062 /*
5063 * Try open the device.
5064 */
5065 HANDLE hFile = RTNT_INVALID_HANDLE_VALUE;
5066 IO_STATUS_BLOCK Ios = RTNT_IO_STATUS_BLOCK_INITIALIZER;
5067 UNICODE_STRING NtName = RTNT_CONSTANT_UNISTR(SUPDRV_NT_DEVICE_NAME_ERROR_INFO);
5068 OBJECT_ATTRIBUTES ObjAttr;
5069 InitializeObjectAttributes(&ObjAttr, &NtName, OBJ_CASE_INSENSITIVE, NULL /*hRootDir*/, NULL /*pSecDesc*/);
5070 NTSTATUS rcNt = NtCreateFile(&hFile,
5071 GENERIC_READ, /* No SYNCHRONIZE. */
5072 &ObjAttr,
5073 &Ios,
5074 NULL /* Allocation Size*/,
5075 FILE_ATTRIBUTE_NORMAL,
5076 FILE_SHARE_READ | FILE_SHARE_WRITE,
5077 FILE_OPEN,
5078 FILE_NON_DIRECTORY_FILE, /* No FILE_SYNCHRONOUS_IO_NONALERT. */
5079 NULL /*EaBuffer*/,
5080 0 /*EaLength*/);
5081 if (NT_SUCCESS(rcNt))
5082 rcNt = Ios.Status;
5083 if (NT_SUCCESS(rcNt))
5084 {
5085 /*
5086 * Try read error info.
5087 */
5088 size_t cchPrefix = strlen(pszPrefix);
5089 if (cchPrefix + 3 < cbErrorInfo)
5090 {
5091 LARGE_INTEGER offRead;
5092 offRead.QuadPart = 0;
5093 rcNt = NtReadFile(hFile, NULL /*hEvent*/, NULL /*ApcRoutine*/, NULL /*ApcContext*/, &Ios,
5094 &pszErrorInfo[cchPrefix], (ULONG)(cbErrorInfo - cchPrefix - 1), &offRead, NULL);
5095 if (NT_SUCCESS(rcNt) && NT_SUCCESS(Ios.Status) && Ios.Information > 0)
5096 {
5097 memcpy(pszErrorInfo, pszPrefix, cchPrefix);
5098 pszErrorInfo[RT_MIN(cbErrorInfo - 1, cchPrefix + Ios.Information)] = '\0';
5099 SUP_DPRINTF(("supR3HardenedWinReadErrorInfoDevice: '%s'", &pszErrorInfo[cchPrefix]));
5100 }
5101 else
5102 {
5103 *pszErrorInfo = '\0';
5104 if (rcNt != STATUS_END_OF_FILE || Ios.Status != STATUS_END_OF_FILE)
5105 SUP_DPRINTF(("supR3HardenedWinReadErrorInfoDevice: NtReadFile -> %#x / %#x / %p\n",
5106 rcNt, Ios.Status, Ios.Information));
5107 }
5108 }
5109 else
5110 RTStrCopy(pszErrorInfo, cbErrorInfo, "error info buffer too small");
5111 NtClose(hFile);
5112 }
5113 else
5114 SUP_DPRINTF(("supR3HardenedWinReadErrorInfoDevice: NtCreateFile -> %#x\n", rcNt));
5115
5116 return pszErrorInfo;
5117}
5118
5119
5120
5121/**
5122 * Checks if the driver exists.
5123 *
5124 * This checks whether the driver is present in the /Driver object directory.
5125 * Drivers being initialized or terminated will have an object there
5126 * before/after their devices nodes are created/deleted.
5127 *
5128 * @returns true if it exists, false if not.
5129 * @param pszDriver The driver name.
5130 */
5131static bool supR3HardenedWinDriverExists(const char *pszDriver)
5132{
5133 /*
5134 * Open the driver object directory.
5135 */
5136 UNICODE_STRING NtDirName = RTNT_CONSTANT_UNISTR(L"\\Driver");
5137
5138 OBJECT_ATTRIBUTES ObjAttr;
5139 InitializeObjectAttributes(&ObjAttr, &NtDirName, OBJ_CASE_INSENSITIVE, NULL /*hRootDir*/, NULL /*pSecDesc*/);
5140
5141 HANDLE hDir;
5142 NTSTATUS rcNt = NtOpenDirectoryObject(&hDir, DIRECTORY_QUERY | FILE_LIST_DIRECTORY, &ObjAttr);
5143#ifdef VBOX_STRICT
5144 SUPR3HARDENED_ASSERT_NT_SUCCESS(rcNt);
5145#endif
5146 if (!NT_SUCCESS(rcNt))
5147 return true;
5148
5149 /*
5150 * Enumerate it, looking for the driver.
5151 */
5152 bool fFound = true;
5153 ULONG uObjDirCtx = 0;
5154 do
5155 {
5156 uint32_t abBuffer[_64K + _1K];
5157 ULONG cbActual;
5158 rcNt = NtQueryDirectoryObject(hDir,
5159 abBuffer,
5160 sizeof(abBuffer) - 4, /* minus four for string terminator space. */
5161 FALSE /*ReturnSingleEntry */, <