VirtualBox

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

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

HostDrivers/Support/SUPR3HardenedMain-win: bugref:8810: version tweak

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