VirtualBox

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

Last change on this file since 52425 was 52406, checked in by vboxsync, 11 years ago

SUP: No need to repeat the IPRT signature check when we just want to do WinVerifyTrust.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 148.4 KB
Line 
1/* $Id: SUPR3HardenedMain-win.cpp 52406 2014-08-19 03:03:28Z vboxsync $ */
2/** @file
3 * VirtualBox Support Library - Hardened main(), windows bits.
4 */
5
6/*
7 * Copyright (C) 2006-2014 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* Header Files *
29*******************************************************************************/
30#include <iprt/nt/nt-and-windows.h>
31#include <AccCtrl.h>
32#include <AclApi.h>
33#ifndef PROCESS_SET_LIMITED_INFORMATION
34# define PROCESS_SET_LIMITED_INFORMATION 0x2000
35#endif
36#ifndef LOAD_LIBRARY_SEARCH_APPLICATION_DIR
37# define LOAD_LIBRARY_SEARCH_APPLICATION_DIR 0x200
38# define LOAD_LIBRARY_SEARCH_SYSTEM32 0x800
39#endif
40
41#include <VBox/sup.h>
42#include <VBox/err.h>
43#include <VBox/dis.h>
44#include <iprt/ctype.h>
45#include <iprt/string.h>
46#include <iprt/initterm.h>
47#include <iprt/param.h>
48#include <iprt/path.h>
49#include <iprt/zero.h>
50
51#include "SUPLibInternal.h"
52#include "win/SUPHardenedVerify-win.h"
53#include "../SUPDrvIOC.h"
54
55#ifndef IMAGE_SCN_TYPE_NOLOAD
56# define IMAGE_SCN_TYPE_NOLOAD 0x00000002
57#endif
58
59
60/*******************************************************************************
61* Defined Constants And Macros *
62*******************************************************************************/
63/** The first argument of a respawed stub when respawned for the first time.
64 * This just needs to be unique enough to avoid most confusion with real
65 * executable names, there are other checks in place to make sure we've respanwed. */
66#define SUPR3_RESPAWN_1_ARG0 "5cb9562b-4b8c-d13f-6bc4-3da9b0f37da6-suplib-2ndchild"
67
68/** The first argument of a respawed stub when respawned for the second time.
69 * This just needs to be unique enough to avoid most confusion with real
70 * executable names, there are other checks in place to make sure we've respanwed. */
71#define SUPR3_RESPAWN_2_ARG0 "5cb9562b-4b8c-d13f-6bc4-3da9b0f37da6-suplib-3rdchild"
72
73/** Unconditional assertion. */
74#define SUPR3HARDENED_ASSERT(a_Expr) \
75 do { \
76 if (!(a_Expr)) \
77 supR3HardenedFatal("%s: %s", __FUNCTION__, #a_Expr); \
78 } while (0)
79
80/** Unconditional assertion of NT_SUCCESS. */
81#define SUPR3HARDENED_ASSERT_NT_SUCCESS(a_Expr) \
82 do { \
83 NTSTATUS rcNtAssert = (a_Expr); \
84 if (!NT_SUCCESS(rcNtAssert)) \
85 supR3HardenedFatal("%s: %s -> %#x", __FUNCTION__, #a_Expr, rcNtAssert); \
86 } while (0)
87
88/** Unconditional assertion of a WIN32 API returning non-FALSE. */
89#define SUPR3HARDENED_ASSERT_WIN32_SUCCESS(a_Expr) \
90 do { \
91 BOOL fRcAssert = (a_Expr); \
92 if (fRcAssert == FALSE) \
93 supR3HardenedFatal("%s: %s -> %#x", __FUNCTION__, #a_Expr, GetLastError()); \
94 } while (0)
95
96
97/*******************************************************************************
98* Structures and Typedefs *
99*******************************************************************************/
100/**
101 * Security descriptor cleanup structure.
102 */
103typedef struct MYSECURITYCLEANUP
104{
105 union
106 {
107 SID Sid;
108 uint8_t abPadding[SECURITY_MAX_SID_SIZE];
109 } Everyone, Owner, User, Login;
110 union
111 {
112 ACL AclHdr;
113 uint8_t abPadding[1024];
114 } Acl;
115 PSECURITY_DESCRIPTOR pSecDesc;
116} MYSECURITYCLEANUP;
117/** Pointer to security cleanup structure. */
118typedef MYSECURITYCLEANUP *PMYSECURITYCLEANUP;
119
120
121/**
122 * Image verifier cache entry.
123 */
124typedef struct VERIFIERCACHEENTRY
125{
126 /** Pointer to the next entry with the same hash value. */
127 struct VERIFIERCACHEENTRY * volatile pNext;
128 /** Next entry in the WinVerifyTrust todo list. */
129 struct VERIFIERCACHEENTRY * volatile pNextTodoWvt;
130
131 /** The file handle. */
132 HANDLE hFile;
133 /** If fIndexNumber is set, this is an file system internal file identifier. */
134 LARGE_INTEGER IndexNumber;
135 /** The path hash value. */
136 uint32_t uHash;
137 /** The verification result. */
138 int rc;
139 /** Whether IndexNumber is valid */
140 bool fIndexNumberValid;
141 /** Whether verified by WinVerifyTrust. */
142 bool volatile fWinVerifyTrust;
143 /** cwcPath * sizeof(RTUTF16). */
144 uint16_t cbPath;
145 /** The full path of this entry (variable size). */
146 RTUTF16 wszPath[1];
147} VERIFIERCACHEENTRY;
148/** Pointer to an image verifier path entry. */
149typedef VERIFIERCACHEENTRY *PVERIFIERCACHEENTRY;
150
151
152/**
153 * Name of an import DLL that we need to check out.
154 */
155typedef struct VERIFIERCACHEIMPORT
156{
157 /** Pointer to the next DLL in the list. */
158 struct VERIFIERCACHEIMPORT * volatile pNext;
159 /** The length of pwszAltSearchDir if available. */
160 uint32_t cwcAltSearchDir;
161 /** This points the directory containing the DLL needing it, this will be
162 * NULL for a System32 DLL. */
163 PWCHAR pwszAltSearchDir;
164 /** The name of the import DLL (variable length). */
165 char szName[1];
166} VERIFIERCACHEIMPORT;
167/** Pointer to a import DLL that needs checking out. */
168typedef VERIFIERCACHEIMPORT *PVERIFIERCACHEIMPORT;
169
170
171/*******************************************************************************
172* Global Variables *
173*******************************************************************************/
174/** @name Global variables initialized by suplibHardenedWindowsMain.
175 * @{ */
176/** Combined windows NT version number. See SUP_MAKE_NT_VER_COMBINED. */
177uint32_t g_uNtVerCombined = 0;
178/** Count calls to the special main function for linking santity checks. */
179static uint32_t volatile g_cSuplibHardenedWindowsMainCalls;
180/** The UTF-16 windows path to the executable. */
181RTUTF16 g_wszSupLibHardenedExePath[1024];
182/** The NT path of the executable. */
183SUPSYSROOTDIRBUF g_SupLibHardenedExeNtPath;
184/** The offset into g_SupLibHardenedExeNtPath of the executable name (WCHAR,
185 * not byte). This also gives the length of the exectuable directory path,
186 * including a trailing slash. */
187uint32_t g_offSupLibHardenedExeNtName;
188/** @} */
189
190/** @name Hook related variables.
191 * @{ */
192/** The jump back address of the patched NtCreateSection. */
193extern "C" PFNRT g_pfnNtCreateSectionJmpBack = NULL;
194/** Pointer to the bit of assembly code that will perform the original
195 * NtCreateSection operation. */
196static NTSTATUS (NTAPI * g_pfnNtCreateSectionReal)(PHANDLE, ACCESS_MASK, POBJECT_ATTRIBUTES,
197 PLARGE_INTEGER, ULONG, ULONG, HANDLE);
198#if 0
199/** The jump back address of the patched LdrLoadDll. */
200extern "C" PFNRT g_pfnLdrLoadDllJmpBack = NULL;
201#endif
202/** Pointer to the bit of assembly code that will perform the original
203 * LdrLoadDll operation. */
204static NTSTATUS (NTAPI * g_pfnLdrLoadDllReal)(PWSTR, PULONG, PUNICODE_STRING, PHANDLE);
205/** The hash table of verifier cache . */
206static VERIFIERCACHEENTRY * volatile g_apVerifierCache[128];
207/** Queue of cached images which needs WinVerifyTrust to check them. */
208static VERIFIERCACHEENTRY * volatile g_pVerifierCacheTodoWvt = NULL;
209/** Queue of cached images which needs their imports checked. */
210static VERIFIERCACHEIMPORT * volatile g_pVerifierCacheTodoImports = NULL;
211/** @ */
212
213/** Static error info structure used during init. */
214static RTERRINFOSTATIC g_ErrInfoStatic;
215
216/** In the assembly file. */
217extern "C" uint8_t g_abSupHardReadWriteExecPage[PAGE_SIZE];
218
219
220/*******************************************************************************
221* Internal Functions *
222*******************************************************************************/
223static NTSTATUS supR3HardenedScreenImage(HANDLE hFile, bool fImage, PULONG pfAccess, PULONG pfProtect,
224 bool *pfCallRealApi, const char *pszCaller);
225
226#ifdef RT_ARCH_AMD64
227# define SYSCALL(a_Num) DECLASM(void) RT_CONCAT(supR3HardenedJmpBack_NtCreateSection_,a_Num)(void)
228# include "NtCreateSection-template-amd64-syscall-type-1.h"
229# undef SYSCALL
230#endif
231#ifdef RT_ARCH_X86
232# define SYSCALL(a_Num) DECLASM(void) RT_CONCAT(supR3HardenedJmpBack_NtCreateSection_,a_Num)(void)
233# include "NtCreateSection-template-x86-syscall-type-1.h"
234# undef SYSCALL
235#endif
236
237
238
239/**
240 * Simple wide char search routine.
241 *
242 * @returns Pointer to the first location of @a wcNeedle in @a pwszHaystack.
243 * NULL if not found.
244 * @param pwszHaystack Pointer to the string that should be searched.
245 * @param wcNeedle The character to search for.
246 */
247static PRTUTF16 suplibHardenedWStrChr(PCRTUTF16 pwszHaystack, RTUTF16 wcNeedle)
248{
249 for (;;)
250 {
251 RTUTF16 wcCur = *pwszHaystack;
252 if (wcCur == wcNeedle)
253 return (PRTUTF16)pwszHaystack;
254 if (wcCur == '\0')
255 return NULL;
256 pwszHaystack++;
257 }
258}
259
260
261/**
262 * Simple wide char string length routine.
263 *
264 * @returns The number of characters in the given string. (Excludes the
265 * terminator.)
266 * @param pwsz The string.
267 */
268static size_t suplibHardenedWStrLen(PCRTUTF16 pwsz)
269{
270 PCRTUTF16 pwszCur = pwsz;
271 while (*pwszCur != '\0')
272 pwszCur++;
273 return pwszCur - pwsz;
274}
275
276
277/**
278 * Allocate zero filled memory on the heap.
279 *
280 * @returns Pointer to the memory. Will never return NULL, triggers a fatal
281 * error instead.
282 * @param cb The number of bytes to allocate.
283 */
284DECLHIDDEN(void *) suplibHardenedAllocZ(size_t cb)
285{
286 void *pv = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, cb);
287 if (!pv)
288 supR3HardenedFatal("HeapAlloc failed to allocate %zu bytes.\n", cb);
289 return pv;
290}
291
292
293/**
294 * Reallocates memory on the heap.
295 *
296 * @returns Pointer to the resized memory block. Will never return NULL,
297 * triggers a fatal error instead.
298 * @param pvOld The old memory block.
299 * @param cbNew The new block size.
300 */
301DECLHIDDEN(void *) suplibHardenedReAlloc(void *pvOld, size_t cbNew)
302{
303 if (!pvOld)
304 return suplibHardenedAllocZ(cbNew);
305 void *pv = HeapReAlloc(GetProcessHeap(), 0 /*dwFlags*/, pvOld, cbNew);
306 if (!pv)
307 supR3HardenedFatal("HeapReAlloc failed to allocate %zu bytes.\n", cbNew);
308 return pv;
309}
310
311
312/**
313 * Frees memory allocated by suplibHardenedAlloc, suplibHardenedAllocZ or
314 * suplibHardenedReAlloc.
315 *
316 * @param pv Pointer to the memeory to be freed.
317 */
318DECLHIDDEN(void) suplibHardenedFree(void *pv)
319{
320 if (pv)
321 HeapFree(GetProcessHeap(), 0 /* dwFlags*/, pv);
322}
323
324
325/**
326 * Wrapper around LoadLibraryEx that deals with the UTF-8 to UTF-16 conversion
327 * and supplies the right flags.
328 *
329 * @returns Module handle on success, NULL on failure.
330 * @param pszName The full path to the DLL.
331 * @param fSystem32Only Whether to only look for imports in the system32
332 * directory. If set to false, the application
333 * directory is also searched.
334 */
335DECLHIDDEN(void *) supR3HardenedWinLoadLibrary(const char *pszName, bool fSystem32Only)
336{
337 WCHAR wszPath[RTPATH_MAX];
338 PRTUTF16 pwszPath = wszPath;
339 int rc = RTStrToUtf16Ex(pszName, RTSTR_MAX, &pwszPath, RT_ELEMENTS(wszPath), NULL);
340 if (RT_SUCCESS(rc))
341 {
342 while (*pwszPath)
343 {
344 if (*pwszPath == '/')
345 *pwszPath = '\\';
346 pwszPath++;
347 }
348
349 DWORD fFlags = 0;
350 if (g_uNtVerCombined >= SUP_MAKE_NT_VER_SIMPLE(6, 0))
351 {
352 fFlags |= LOAD_LIBRARY_SEARCH_SYSTEM32;
353 if (!fSystem32Only)
354 fFlags |= LOAD_LIBRARY_SEARCH_APPLICATION_DIR;
355 }
356
357 void *pvRet = (void *)LoadLibraryExW(wszPath, NULL /*hFile*/, fFlags);
358
359 /* Vista, W7, W2K8R might not work without KB2533623, so retry with no flags. */
360 if ( !pvRet
361 && fFlags
362 && g_uNtVerCombined < SUP_MAKE_NT_VER_SIMPLE(6, 2)
363 && GetLastError() == ERROR_INVALID_PARAMETER)
364 pvRet = (void *)LoadLibraryExW(wszPath, NULL /*hFile*/, 0);
365
366 return pvRet;
367 }
368 supR3HardenedFatal("RTStrToUtf16Ex failed on '%s': %Rrc", pszName, rc);
369 return NULL;
370}
371
372
373/**
374 * Gets the internal index number of the file.
375 *
376 * @returns True if we got an index number, false if not.
377 * @param hFile The file in question.
378 * @param pIndexNumber where to return the index number.
379 */
380static bool supR3HardenedWinVerifyCacheGetIndexNumber(HANDLE hFile, PLARGE_INTEGER pIndexNumber)
381{
382 IO_STATUS_BLOCK Ios = RTNT_IO_STATUS_BLOCK_INITIALIZER;
383 NTSTATUS rcNt = NtQueryInformationFile(hFile, &Ios, pIndexNumber, sizeof(*pIndexNumber), FileInternalInformation);
384 if (NT_SUCCESS(rcNt))
385 rcNt = Ios.Status;
386#ifdef DEBUG_bird
387 if (!NT_SUCCESS(rcNt))
388 __debugbreak();
389#endif
390 return NT_SUCCESS(rcNt) && pIndexNumber->QuadPart != 0;
391}
392
393
394/**
395 * Calculates the hash value for the given UTF-16 path string.
396 *
397 * @returns Hash value.
398 * @param pUniStr String to hash.
399 */
400static uint32_t supR3HardenedWinVerifyCacheHashPath(PCUNICODE_STRING pUniStr)
401{
402 uint32_t uHash = 0;
403 unsigned cwcLeft = pUniStr->Length / sizeof(WCHAR);
404 PRTUTF16 pwc = pUniStr->Buffer;
405
406 while (cwcLeft-- > 0)
407 {
408 RTUTF16 wc = *pwc++;
409 if (wc < 0x80)
410 wc = wc != '/' ? RT_C_TO_LOWER(wc) : '\\';
411 uHash = wc + (uHash << 6) + (uHash << 16) - uHash;
412 }
413 return uHash;
414}
415
416
417/**
418 * Calculates the hash value for a directory + filename combo as if they were
419 * one single string.
420 *
421 * @returns Hash value.
422 * @param pawcDir The directory name.
423 * @param cwcDir The length of the directory name. RTSTR_MAX if
424 * not available.
425 * @param pszName The import name (UTF-8).
426 */
427static uint32_t supR3HardenedWinVerifyCacheHashDirAndFile(PCRTUTF16 pawcDir, uint32_t cwcDir, const char *pszName)
428{
429 uint32_t uHash = 0;
430 while (cwcDir-- > 0)
431 {
432 RTUTF16 wc = *pawcDir++;
433 if (wc < 0x80)
434 wc = wc != '/' ? RT_C_TO_LOWER(wc) : '\\';
435 uHash = wc + (uHash << 6) + (uHash << 16) - uHash;
436 }
437
438 unsigned char ch = '\\';
439 uHash = ch + (uHash << 6) + (uHash << 16) - uHash;
440
441 while ((ch = *pszName++) != '\0')
442 {
443 ch = RT_C_TO_LOWER(ch);
444 uHash = ch + (uHash << 6) + (uHash << 16) - uHash;
445 }
446
447 return uHash;
448}
449
450
451/**
452 * Verify string cache compare function.
453 *
454 * @returns true if the strings match, false if not.
455 * @param pawcLeft The left hand string.
456 * @param pawcRight The right hand string.
457 * @param cwcToCompare The number of chars to compare.
458 */
459static bool supR3HardenedWinVerifyCacheIsMatch(PCRTUTF16 pawcLeft, PCRTUTF16 pawcRight, uint32_t cwcToCompare)
460{
461 /* Try a quick memory compare first. */
462 if (memcmp(pawcLeft, pawcRight, cwcToCompare * sizeof(RTUTF16)) == 0)
463 return true;
464
465 /* Slow char by char compare. */
466 while (cwcToCompare-- > 0)
467 {
468 RTUTF16 wcLeft = *pawcLeft++;
469 RTUTF16 wcRight = *pawcRight++;
470 if (wcLeft != wcRight)
471 {
472 wcLeft = wcLeft != '/' ? RT_C_TO_LOWER(wcLeft) : '\\';
473 wcLeft = wcRight != '/' ? RT_C_TO_LOWER(wcRight) : '\\';
474 if (wcLeft != wcRight)
475 return false;
476 }
477 }
478
479 return true;
480}
481
482
483
484/**
485 * Inserts the given verifier result into the cache.
486 *
487 * @param pUniStr The full path of the image.
488 * @param hFile The file handle - must either be entered into
489 * the cache or closed.
490 * @param rc The verifier result.
491 * @param fWinVerifyTrust Whether verified by WinVerifyTrust or not.
492 */
493static void supR3HardenedWinVerifyCacheInsert(PCUNICODE_STRING pUniStr, HANDLE hFile, int rc, bool fWinVerifyTrust)
494{
495 /*
496 * Allocate and initalize a new entry.
497 */
498 PVERIFIERCACHEENTRY pEntry = (PVERIFIERCACHEENTRY)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY,
499 sizeof(VERIFIERCACHEENTRY) + pUniStr->Length);
500 if (pEntry)
501 {
502 pEntry->pNext = NULL;
503 pEntry->pNextTodoWvt = NULL;
504 pEntry->hFile = hFile;
505 pEntry->rc = rc;
506 pEntry->uHash = supR3HardenedWinVerifyCacheHashPath(pUniStr);
507 pEntry->fWinVerifyTrust = fWinVerifyTrust;
508 pEntry->cbPath = pUniStr->Length;
509 memcpy(pEntry->wszPath, pUniStr->Buffer, pUniStr->Length);
510 pEntry->wszPath[pUniStr->Length / sizeof(WCHAR)] = '\0';
511 pEntry->fIndexNumberValid = supR3HardenedWinVerifyCacheGetIndexNumber(hFile, &pEntry->IndexNumber);
512
513 /*
514 * Try insert it, careful with concurrent code as well as potential duplicates.
515 */
516 uint32_t iHashTab = pEntry->uHash % RT_ELEMENTS(g_apVerifierCache);
517 VERIFIERCACHEENTRY * volatile *ppEntry = &g_apVerifierCache[iHashTab];
518 for (;;)
519 {
520 if (ASMAtomicCmpXchgPtr(ppEntry, pEntry, NULL))
521 {
522 if (!fWinVerifyTrust)
523 do
524 pEntry->pNextTodoWvt = g_pVerifierCacheTodoWvt;
525 while (!ASMAtomicCmpXchgPtr(&g_pVerifierCacheTodoWvt, pEntry, pEntry->pNextTodoWvt));
526
527 SUP_DPRINTF(("supR3HardenedWinVerifyCacheInsert: %ls\n", pUniStr->Buffer));
528 return;
529 }
530
531 PVERIFIERCACHEENTRY pOther = *ppEntry;
532 if (!pOther)
533 continue;
534 if ( pOther->uHash == pEntry->uHash
535 && pOther->cbPath == pEntry->cbPath
536 && memcmp(pOther->wszPath, pEntry->wszPath, pEntry->cbPath) == 0)
537 break;
538 ppEntry = &pOther->pNext;
539 }
540
541 /* Duplicate entry. */
542#ifdef DEBUG_bird
543 __debugbreak();
544#endif
545 HeapFree(GetProcessHeap(), 0 /* dwFlags*/, pEntry);
546 }
547 NtClose(hFile);
548}
549
550
551/**
552 * Looks up an entry in the verifier hash table.
553 *
554 * @return Pointer to the entry on if found, NULL if not.
555 * @param pUniStr The full path of the image.
556 * @param hFile The file handle.
557 */
558static PVERIFIERCACHEENTRY supR3HardenedWinVerifyCacheLookup(PCUNICODE_STRING pUniStr, HANDLE hFile)
559{
560 PRTUTF16 const pwszPath = pUniStr->Buffer;
561 uint16_t const cbPath = pUniStr->Length;
562 uint32_t uHash = supR3HardenedWinVerifyCacheHashPath(pUniStr);
563 uint32_t iHashTab = uHash % RT_ELEMENTS(g_apVerifierCache);
564 PVERIFIERCACHEENTRY pCur = g_apVerifierCache[iHashTab];
565 while (pCur)
566 {
567 if ( pCur->uHash == uHash
568 && pCur->cbPath == cbPath
569 && supR3HardenedWinVerifyCacheIsMatch(pCur->wszPath, pwszPath, cbPath / sizeof(RTUTF16)))
570 {
571
572 if (!pCur->fIndexNumberValid)
573 return pCur;
574 LARGE_INTEGER IndexNumber;
575 bool fIndexNumberValid = supR3HardenedWinVerifyCacheGetIndexNumber(hFile, &IndexNumber);
576 if ( fIndexNumberValid
577 && IndexNumber.QuadPart == pCur->IndexNumber.QuadPart)
578 return pCur;
579#ifdef DEBUG_bird
580 __debugbreak();
581#endif
582 }
583 pCur = pCur->pNext;
584 }
585 return NULL;
586}
587
588
589/**
590 * Looks up an import DLL in the verifier hash table.
591 *
592 * @return Pointer to the entry on if found, NULL if not.
593 * @param pawcDir The directory name.
594 * @param cwcDir The length of the directory name.
595 * @param pszName The import name (UTF-8).
596 */
597static PVERIFIERCACHEENTRY supR3HardenedWinVerifyCacheLookupImport(PCRTUTF16 pawcDir, uint32_t cwcDir, const char *pszName)
598{
599 uint32_t uHash = supR3HardenedWinVerifyCacheHashDirAndFile(pawcDir, cwcDir, pszName);
600 uint32_t iHashTab = uHash % RT_ELEMENTS(g_apVerifierCache);
601 uint32_t const cbPath = (uint32_t)((cwcDir + 1 + strlen(pszName)) * sizeof(RTUTF16));
602 PVERIFIERCACHEENTRY pCur = g_apVerifierCache[iHashTab];
603 while (pCur)
604 {
605 if ( pCur->uHash == uHash
606 && pCur->cbPath == cbPath)
607 {
608 if (supR3HardenedWinVerifyCacheIsMatch(pCur->wszPath, pawcDir, cwcDir))
609 {
610 if (pCur->wszPath[cwcDir] == '\\' || pCur->wszPath[cwcDir] == '/')
611 {
612 if (RTUtf16ICmpAscii(&pCur->wszPath[cwcDir + 1], pszName))
613 {
614 return pCur;
615 }
616 }
617 }
618 }
619
620 pCur = pCur->pNext;
621 }
622 return NULL;
623}
624
625
626/**
627 * Schedules the import DLLs for verification and entry into the cache.
628 *
629 * @param hLdrMod The loader module which imports should be
630 * scheduled for verification.
631 * @param pwszName The full NT path of the module.
632 */
633DECLHIDDEN(void) supR3HardenedWinVerifyCacheScheduleImports(RTLDRMOD hLdrMod, PCRTUTF16 pwszName)
634{
635 /*
636 * Any imports?
637 */
638 uint32_t cImports;
639 int rc = RTLdrQueryPropEx(hLdrMod, RTLDRPROP_IMPORT_COUNT, NULL /*pvBits*/, &cImports, sizeof(cImports), NULL);
640 if (RT_SUCCESS(rc))
641 {
642 if (cImports)
643 {
644 /*
645 * Figure out the DLL directory from pwszName.
646 */
647 PCRTUTF16 pawcDir = pwszName;
648 uint32_t cwcDir = 0;
649 uint32_t i = 0;
650 RTUTF16 wc;
651 while ((wc = pawcDir[i++]) != '\0')
652 if ((wc == '\\' || wc == '/' || wc == ':') && cwcDir + 2 != i)
653 cwcDir = i - 1;
654 if ( g_System32NtPath.UniStr.Length / sizeof(WCHAR) == cwcDir
655 && supR3HardenedWinVerifyCacheIsMatch(pawcDir, g_System32NtPath.UniStr.Buffer, cwcDir))
656 pawcDir = NULL;
657
658 /*
659 * Enumerate the imports.
660 */
661 for (i = 0; i < cImports; i++)
662 {
663 union
664 {
665 char szName[256];
666 uint32_t iImport;
667 } uBuf;
668 uBuf.iImport = i;
669 rc = RTLdrQueryPropEx(hLdrMod, RTLDRPROP_IMPORT_MODULE, NULL /*pvBits*/, &uBuf, sizeof(uBuf), NULL);
670 if (RT_SUCCESS(rc))
671 {
672 /*
673 * Skip kernel32, ntdll and API set stuff.
674 */
675 RTStrToLower(uBuf.szName);
676 if ( RTStrCmp(uBuf.szName, "kernel32.dll") == 0
677 || RTStrCmp(uBuf.szName, "kernelbase.dll") == 0
678 || RTStrCmp(uBuf.szName, "ntdll.dll") == 0
679 || RTStrNCmp(uBuf.szName, RT_STR_TUPLE("api-ms-win-")) == 0 )
680 {
681 continue;
682 }
683
684 /*
685 * Skip to the next one if it's already in the cache.
686 */
687 if (supR3HardenedWinVerifyCacheLookupImport(g_System32NtPath.UniStr.Buffer,
688 g_System32NtPath.UniStr.Length / sizeof(WCHAR),
689 uBuf.szName) != NULL)
690 {
691 SUP_DPRINTF(("supR3HardenedWinVerifyCacheScheduleImports: '%s' cached for system32\n", uBuf.szName));
692 continue;
693 }
694 if (supR3HardenedWinVerifyCacheLookupImport(g_SupLibHardenedExeNtPath.UniStr.Buffer,
695 g_offSupLibHardenedExeNtName,
696 uBuf.szName) != NULL)
697 {
698 SUP_DPRINTF(("supR3HardenedWinVerifyCacheScheduleImports: '%s' cached for appdir\n", uBuf.szName));
699 continue;
700 }
701 if (pawcDir && supR3HardenedWinVerifyCacheLookupImport(pawcDir, cwcDir, uBuf.szName) != NULL)
702 {
703 SUP_DPRINTF(("supR3HardenedWinVerifyCacheScheduleImports: '%s' cached for dll dir\n", uBuf.szName));
704 continue;
705 }
706
707 /* We could skip already scheduled modules, but that'll require serialization and extra work... */
708
709 /*
710 * Add it to the todo list.
711 */
712 SUP_DPRINTF(("supR3HardenedWinVerifyCacheScheduleImports: Import todo: #%u '%s'.\n", i, uBuf.szName));
713 uint32_t cbName = (uint32_t)strlen(uBuf.szName) + 1;
714 uint32_t cbNameAligned = RT_ALIGN_32(cbName, sizeof(RTUTF16));
715 uint32_t cbNeeded = RT_OFFSETOF(VERIFIERCACHEIMPORT, szName[cbNameAligned])
716 + (pawcDir ? (cwcDir + 1) * sizeof(RTUTF16) : 0);
717 PVERIFIERCACHEIMPORT pImport = (PVERIFIERCACHEIMPORT)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, cbNeeded);
718 if (pImport)
719 {
720 /* Init it. */
721 memcpy(pImport->szName, uBuf.szName, cbName);
722 if (!pawcDir)
723 {
724 pImport->cwcAltSearchDir = 0;
725 pImport->pwszAltSearchDir = NULL;
726 }
727 else
728 {
729 pImport->cwcAltSearchDir = cwcDir;
730 pImport->pwszAltSearchDir = (PRTUTF16)&pImport->szName[cbNameAligned];
731 memcpy(pImport->pwszAltSearchDir, pawcDir, cwcDir * sizeof(RTUTF16));
732 pImport->pwszAltSearchDir[cwcDir] = '\0';
733 }
734
735 /* Insert it. */
736 do
737 pImport->pNext = g_pVerifierCacheTodoImports;
738 while (!ASMAtomicCmpXchgPtr(&g_pVerifierCacheTodoImports, pImport, pImport->pNext));
739 }
740 }
741 else
742 SUP_DPRINTF(("RTLDRPROP_IMPORT_MODULE failed with rc=%Rrc i=%#x on '%ls'\n", rc, i, pwszName));
743 }
744 }
745 else
746 SUP_DPRINTF(("'%ls' has no imports\n", pwszName));
747 }
748 else
749 SUP_DPRINTF(("RTLDRPROP_IMPORT_COUNT failed with rc=%Rrc on '%ls'\n", rc, pwszName));
750}
751
752
753/**
754 * Processes the list of import todos.
755 */
756static void supR3HardenedWinVerifyCacheProcessImportTodos(void)
757{
758 /*
759 * Work until we've got nothing more todo.
760 */
761 for (;;)
762 {
763 PVERIFIERCACHEIMPORT pTodo = ASMAtomicXchgPtrT(&g_pVerifierCacheTodoImports, NULL, PVERIFIERCACHEIMPORT);
764 if (!pTodo)
765 break;
766 do
767 {
768 PVERIFIERCACHEIMPORT pCur = pTodo;
769 pTodo = pTodo->pNext;
770
771 /*
772 * Not in the cached already?
773 */
774 if ( !supR3HardenedWinVerifyCacheLookupImport(g_System32NtPath.UniStr.Buffer,
775 g_System32NtPath.UniStr.Length / sizeof(WCHAR),
776 pCur->szName)
777 && !supR3HardenedWinVerifyCacheLookupImport(g_SupLibHardenedExeNtPath.UniStr.Buffer,
778 g_offSupLibHardenedExeNtName,
779 pCur->szName)
780 && ( pCur->cwcAltSearchDir == 0
781 || !supR3HardenedWinVerifyCacheLookupImport(pCur->pwszAltSearchDir, pCur->cwcAltSearchDir, pCur->szName)) )
782 {
783 /*
784 * Try locate the imported DLL and open it.
785 */
786 SUP_DPRINTF(("supR3HardenedWinVerifyCacheProcessImportTodos: Processing '%s'...\n", pCur->szName));
787
788 NTSTATUS rcNt;
789 HANDLE hFile = INVALID_HANDLE_VALUE;
790 RTUTF16 wszPath[260 + 260]; /* Assumes we've limited the import name length to 256. */
791 AssertCompile(sizeof(wszPath) > sizeof(g_System32NtPath));
792 struct
793 {
794 PRTUTF16 pawcDir;
795 uint32_t cwcDir;
796 } Tmp, aDirs[] =
797 {
798 { g_System32NtPath.UniStr.Buffer, g_System32NtPath.UniStr.Length / sizeof(WCHAR) },
799 { g_SupLibHardenedExeNtPath.UniStr.Buffer, g_offSupLibHardenedExeNtName - 1 },
800 { pCur->pwszAltSearchDir, pCur->cwcAltSearchDir },
801 };
802
803 /* Search System32 first, unless it's a 'V*' or 'm*' name, the latter for msvcrt. */
804 if ( pCur->szName[0] == 'v'
805 || pCur->szName[0] == 'V'
806 || pCur->szName[0] == 'm'
807 || pCur->szName[0] == 'M')
808 {
809 Tmp = aDirs[0];
810 aDirs[0] = aDirs[1];
811 aDirs[1] = Tmp;
812 }
813
814 for (uint32_t i = 0; i < RT_ELEMENTS(aDirs); i++)
815 {
816 if (aDirs[i].pawcDir && aDirs[i].cwcDir && aDirs[i].cwcDir < RT_ELEMENTS(wszPath) / 3 * 2)
817 {
818 memcpy(wszPath, aDirs[i].pawcDir, aDirs[i].cwcDir * sizeof(RTUTF16));
819 uint32_t cwc = aDirs[i].cwcDir;
820 wszPath[cwc++] = '\\';
821 size_t cwcName = RT_ELEMENTS(wszPath) - cwc;
822 PRTUTF16 pwszName = &wszPath[cwc];
823 int rc = RTStrToUtf16Ex(pCur->szName, RTSTR_MAX, &pwszName, cwcName, &cwcName);
824 if (RT_SUCCESS(rc))
825 {
826 IO_STATUS_BLOCK Ios = RTNT_IO_STATUS_BLOCK_INITIALIZER;
827 UNICODE_STRING NtName;
828 NtName.Buffer = wszPath;
829 NtName.Length = (USHORT)((cwc + cwcName) * sizeof(WCHAR));
830 NtName.MaximumLength = NtName.Length + sizeof(WCHAR);
831 OBJECT_ATTRIBUTES ObjAttr;
832 InitializeObjectAttributes(&ObjAttr, &NtName, OBJ_CASE_INSENSITIVE, NULL /*hRootDir*/, NULL /*pSecDesc*/);
833
834 rcNt = NtCreateFile(&hFile,
835 FILE_READ_DATA | READ_CONTROL | SYNCHRONIZE,
836 &ObjAttr,
837 &Ios,
838 NULL /* Allocation Size*/,
839 FILE_ATTRIBUTE_NORMAL,
840 FILE_SHARE_READ,
841 FILE_OPEN,
842 FILE_NON_DIRECTORY_FILE | FILE_SYNCHRONOUS_IO_NONALERT,
843 NULL /*EaBuffer*/,
844 0 /*EaLength*/);
845 if (NT_SUCCESS(rcNt))
846 rcNt = Ios.Status;
847 if (NT_SUCCESS(rcNt))
848 break;
849 hFile = INVALID_HANDLE_VALUE;
850 }
851 else
852 SUP_DPRINTF(("supR3HardenedWinVerifyCacheProcessImportTodos: RTStrToUtf16Ex failed: %Rrc\n", rc));
853 }
854 }
855
856 /*
857 * If we successfully opened it, verify it and cache the result.
858 */
859 if (hFile != INVALID_HANDLE_VALUE)
860 {
861 SUP_DPRINTF(("supR3HardenedWinVerifyCacheProcessImportTodos: '%s' -> '%ls'\n", pCur->szName, wszPath));
862
863 ULONG fAccess = 0;
864 ULONG fProtect = 0;
865 bool fCallRealApi = false;
866 rcNt = supR3HardenedScreenImage(hFile, true /*fImage*/, &fAccess, &fProtect, &fCallRealApi, "Imports");
867 NtClose(hFile);
868 }
869 else
870 SUP_DPRINTF(("supR3HardenedWinVerifyCacheProcessImportTodos: Failed to locate '%s'\n", pCur->szName));
871 }
872 else
873 SUP_DPRINTF(("supR3HardenedWinVerifyCacheProcessImportTodos: '%s' is in the cache.\n", pCur->szName));
874
875 HeapFree(GetProcessHeap(), 0 /* dwFlags*/, pCur);
876 } while (pTodo);
877 }
878}
879
880
881/**
882 * Checks whether the path could be containing alternative 8.3 names generated
883 * by NTFS, FAT, or other similar file systems.
884 *
885 * @returns Pointer to the first component that might be an 8.3 name, NULL if
886 * not 8.3 path.
887 * @param pwszPath The path to check.
888 */
889static PRTUTF16 supR3HardenedWinIsPossible8dot3Path(PCRTUTF16 pwszPath)
890{
891 PCRTUTF16 pwszName = pwszPath;
892 for (;;)
893 {
894 RTUTF16 wc = *pwszPath++;
895 if (wc == '~')
896 {
897 /* Could check more here before jumping to conclusions... */
898 if (pwszPath - pwszName <= 8+1+3)
899 return (PRTUTF16)pwszName;
900 }
901 else if (wc == '\\' || wc == '/' || wc == ':')
902 pwszName = pwszPath;
903 else if (wc == 0)
904 break;
905 }
906 return NULL;
907}
908
909
910/**
911 * Fixes up a path possibly containing one or more alternative 8-dot-3 style
912 * components.
913 *
914 * The path is fixed up in place. Errors are ignored.
915 *
916 * @param hFile The handle to the file which path we're fixing up.
917 * @param pUniStr The path to fix up. MaximumLength is the max buffer
918 * length.
919 */
920static void supR3HardenedWinFix8dot3Path(HANDLE hFile, PUNICODE_STRING pUniStr)
921{
922 /*
923 * We could use FileNormalizedNameInformation here and slap the volume device
924 * path in front of the result, but it's only supported since windows 8.0
925 * according to some docs... So we expand all supicious names.
926 */
927 PRTUTF16 pwszFix = pUniStr->Buffer;
928 while (*pwszFix)
929 {
930 pwszFix = supR3HardenedWinIsPossible8dot3Path(pwszFix);
931 if (pwszFix == NULL)
932 break;
933
934 RTUTF16 wc;
935 PRTUTF16 pwszFixEnd = pwszFix;
936 while ((wc = *pwszFixEnd) != '\0' && wc != '\\' && wc != '//')
937 pwszFixEnd++;
938 if (wc == '\0')
939 break;
940
941 RTUTF16 const wcSaved = *pwszFix;
942 *pwszFix = '\0'; /* paranoia. */
943
944 UNICODE_STRING NtDir;
945 NtDir.Buffer = pUniStr->Buffer;
946 NtDir.Length = NtDir.MaximumLength = (USHORT)((pwszFix - pUniStr->Buffer) * sizeof(WCHAR));
947
948 HANDLE hDir = RTNT_INVALID_HANDLE_VALUE;
949 IO_STATUS_BLOCK Ios = RTNT_IO_STATUS_BLOCK_INITIALIZER;
950
951 OBJECT_ATTRIBUTES ObjAttr;
952 InitializeObjectAttributes(&ObjAttr, &NtDir, OBJ_CASE_INSENSITIVE, NULL /*hRootDir*/, NULL /*pSecDesc*/);
953
954 NTSTATUS rcNt = NtCreateFile(&hDir,
955 FILE_READ_DATA | SYNCHRONIZE,
956 &ObjAttr,
957 &Ios,
958 NULL /* Allocation Size*/,
959 FILE_ATTRIBUTE_NORMAL,
960 FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
961 FILE_OPEN,
962 FILE_DIRECTORY_FILE | FILE_OPEN_FOR_BACKUP_INTENT | FILE_SYNCHRONOUS_IO_NONALERT,
963 NULL /*EaBuffer*/,
964 0 /*EaLength*/);
965 *pwszFix = wcSaved;
966 if (NT_SUCCESS(rcNt))
967 {
968 union
969 {
970 FILE_BOTH_DIR_INFORMATION Info;
971 uint8_t abBuffer[sizeof(FILE_BOTH_DIR_INFORMATION) + 2048 * sizeof(WCHAR)];
972 } uBuf;
973 RT_ZERO(uBuf);
974
975 IO_STATUS_BLOCK Ios = RTNT_IO_STATUS_BLOCK_INITIALIZER;
976 UNICODE_STRING NtFilterStr;
977 NtFilterStr.Buffer = pwszFix;
978 NtFilterStr.Length = (USHORT)((uintptr_t)pwszFixEnd - (uintptr_t)pwszFix);
979 NtFilterStr.MaximumLength = NtFilterStr.Length;
980 rcNt = NtQueryDirectoryFile(hDir,
981 NULL /* Event */,
982 NULL /* ApcRoutine */,
983 NULL /* ApcContext */,
984 &Ios,
985 &uBuf,
986 sizeof(uBuf) - sizeof(WCHAR),
987 FileBothDirectoryInformation,
988 FALSE /*ReturnSingleEntry*/,
989 &NtFilterStr,
990 FALSE /*RestartScan */);
991 if (NT_SUCCESS(rcNt) && uBuf.Info.NextEntryOffset == 0) /* There shall only be one entry matching... */
992 {
993 uint32_t offName = uBuf.Info.FileNameLength / sizeof(WCHAR);
994 while (offName > 0 && uBuf.Info.FileName[offName - 1] != '\\' && uBuf.Info.FileName[offName - 1] != '/')
995 offName--;
996 uint32_t cwcNameNew = (uBuf.Info.FileNameLength / sizeof(WCHAR)) - offName;
997 uint32_t cwcNameOld = pwszFixEnd - pwszFix;
998
999 if (cwcNameOld == cwcNameNew)
1000 memcpy(pwszFix, &uBuf.Info.FileName[offName], cwcNameNew * sizeof(WCHAR));
1001 else if ( pUniStr->Length + cwcNameNew * sizeof(WCHAR) - cwcNameOld * sizeof(WCHAR) + sizeof(WCHAR)
1002 <= pUniStr->MaximumLength)
1003 {
1004 size_t cwcLeft = pUniStr->Length - (pwszFixEnd - pUniStr->Buffer) * sizeof(WCHAR) + sizeof(WCHAR);
1005 memmove(&pwszFix[cwcNameNew], pwszFixEnd, cwcLeft * sizeof(WCHAR));
1006 pUniStr->Length -= (USHORT)(cwcNameOld * sizeof(WCHAR));
1007 pUniStr->Length += (USHORT)(cwcNameNew * sizeof(WCHAR));
1008 pwszFixEnd -= cwcNameOld;
1009 pwszFixEnd -= cwcNameNew;
1010 memcpy(pwszFix, &uBuf.Info.FileName[offName], cwcNameNew * sizeof(WCHAR));
1011 }
1012 /* else: ignore overflow. */
1013 }
1014 /* else: ignore failure. */
1015
1016 NtClose(hDir);
1017 }
1018
1019 /* Advance */
1020 pwszFix = pwszFixEnd;
1021 }
1022}
1023
1024
1025static NTSTATUS supR3HardenedScreenImage(HANDLE hFile, bool fImage, PULONG pfAccess, PULONG pfProtect,
1026 bool *pfCallRealApi, const char *pszCaller)
1027{
1028 *pfCallRealApi = false;
1029
1030 /*
1031 * Query the name of the file, making sure to zero terminator the
1032 * string. (2nd half of buffer is used for error info, see below.)
1033 */
1034 union
1035 {
1036 UNICODE_STRING UniStr;
1037 uint8_t abBuffer[sizeof(UNICODE_STRING) + 2048 * sizeof(WCHAR)];
1038 } uBuf;
1039 RT_ZERO(uBuf);
1040 ULONG cbNameBuf;
1041 NTSTATUS rcNt = NtQueryObject(hFile, ObjectNameInformation, &uBuf, sizeof(uBuf) - sizeof(WCHAR) - 128, &cbNameBuf);
1042 if (!NT_SUCCESS(rcNt))
1043 {
1044 supR3HardenedError(VINF_SUCCESS, false,
1045 "supR3HardenedScreenImage/%s: NtQueryObject -> %#x (fImage=%d fProtect=%#x fAccess=%#x)\n",
1046 pszCaller, fImage, *pfProtect, *pfAccess);
1047 return rcNt;
1048 }
1049
1050 if (supR3HardenedWinIsPossible8dot3Path(uBuf.UniStr.Buffer))
1051 {
1052 uBuf.UniStr.MaximumLength = sizeof(uBuf) - 128;
1053 supR3HardenedWinFix8dot3Path(hFile, &uBuf.UniStr);
1054 }
1055
1056 /*
1057 * Check the cache.
1058 * If we haven't done the WinVerifyTrust thing, do it if we can.
1059 */
1060 PVERIFIERCACHEENTRY pCacheHit = supR3HardenedWinVerifyCacheLookup(&uBuf.UniStr, hFile);
1061 if ( pCacheHit
1062 && ( pCacheHit->fWinVerifyTrust
1063 || RT_FAILURE(pCacheHit->rc)
1064 || g_enmSupR3HardenedMainState < SUPR3HARDENEDMAINSTATE_VERIFY_TRUST_READY
1065 || !supHardenedWinIsWinVerifyTrustCallable() ) )
1066 {
1067 SUP_DPRINTF(("supR3HardenedScreenImage/%s: cache hit (%Rrc) on %ls\n", pszCaller, pCacheHit->rc, pCacheHit->wszPath));
1068 if (RT_SUCCESS(pCacheHit->rc))
1069 {
1070 *pfCallRealApi = true;
1071 return STATUS_SUCCESS;
1072 }
1073 supR3HardenedError(VINF_SUCCESS, false,
1074 "supR3HardenedScreenImage/%s: cached rc=%Rrc fImage=%d fProtect=%#x fAccess=%#x %ls\n",
1075 pszCaller, pCacheHit->rc, fImage, *pfProtect, *pfAccess, uBuf.UniStr.Buffer);
1076 return STATUS_TRUST_FAILURE;
1077 }
1078
1079 /*
1080 * On XP the loader might hand us handles with just FILE_EXECUTE and
1081 * SYNCHRONIZE, the means reading will fail later on. Also, we need
1082 * READ_CONTROL access to check the file ownership later on, and non
1083 * of the OS versions seems be giving us that. So, in effect we
1084 * more or less always reopen the file here.
1085 */
1086 HANDLE hMyFile = NULL;
1087 rcNt = NtDuplicateObject(NtCurrentProcess(), hFile, NtCurrentProcess(),
1088 &hMyFile,
1089 FILE_READ_DATA | READ_CONTROL | SYNCHRONIZE,
1090 0 /* Handle attributes*/, 0 /* Options */);
1091 if (!NT_SUCCESS(rcNt))
1092 {
1093 if (rcNt == STATUS_ACCESS_DENIED)
1094 {
1095 IO_STATUS_BLOCK Ios = RTNT_IO_STATUS_BLOCK_INITIALIZER;
1096 OBJECT_ATTRIBUTES ObjAttr;
1097 InitializeObjectAttributes(&ObjAttr, &uBuf.UniStr, OBJ_CASE_INSENSITIVE, NULL /*hRootDir*/, NULL /*pSecDesc*/);
1098
1099 rcNt = NtCreateFile(&hMyFile,
1100 FILE_READ_DATA | READ_CONTROL | SYNCHRONIZE,
1101 &ObjAttr,
1102 &Ios,
1103 NULL /* Allocation Size*/,
1104 FILE_ATTRIBUTE_NORMAL,
1105 FILE_SHARE_READ,
1106 FILE_OPEN,
1107 FILE_NON_DIRECTORY_FILE | FILE_SYNCHRONOUS_IO_NONALERT,
1108 NULL /*EaBuffer*/,
1109 0 /*EaLength*/);
1110 if (NT_SUCCESS(rcNt))
1111 rcNt = Ios.Status;
1112 if (!NT_SUCCESS(rcNt))
1113 {
1114 supR3HardenedError(VINF_SUCCESS, false,
1115 "supR3HardenedScreenImage/%s: Failed to duplicate and open the file: rcNt=%#x hFile=%p %ls\n",
1116 pszCaller, rcNt, hFile, uBuf.UniStr.Buffer);
1117 return rcNt;
1118 }
1119
1120 /* Check that we've got the same file. */
1121 LARGE_INTEGER idMyFile, idInFile;
1122 bool fMyValid = supR3HardenedWinVerifyCacheGetIndexNumber(hMyFile, &idMyFile);
1123 bool fInValid = supR3HardenedWinVerifyCacheGetIndexNumber(hFile, &idInFile);
1124 if ( fMyValid
1125 && ( fMyValid != fInValid
1126 || idMyFile.QuadPart != idInFile.QuadPart))
1127 {
1128 supR3HardenedError(VINF_SUCCESS, false,
1129 "supR3HardenedScreenImage/%s: Re-opened has different ID that input: %#llx vx %#llx (%ls)\n",
1130 pszCaller, rcNt, idMyFile.QuadPart, idInFile.QuadPart, uBuf.UniStr.Buffer);
1131 NtClose(hMyFile);
1132 return STATUS_TRUST_FAILURE;
1133 }
1134 }
1135 else
1136 {
1137 SUP_DPRINTF(("supR3HardenedScreenImage/%s: NtDuplicateObject -> %#x\n", pszCaller, rcNt));
1138#ifdef DEBUG
1139
1140 supR3HardenedError(VINF_SUCCESS, false,
1141 "supR3HardenedScreenImage/%s: NtDuplicateObject(,%#x,) failed: %#x\n", pszCaller, hFile, rcNt);
1142#endif
1143 hMyFile = hFile;
1144 }
1145 }
1146
1147 /*
1148 * Special Kludge for Windows XP and W2K3 and their stupid attempts
1149 * at mapping a hidden XML file called c:\Windows\WindowsShell.Manifest
1150 * with executable access. The image bit isn't set, fortunately.
1151 */
1152 if ( !fImage
1153 && uBuf.UniStr.Length > g_System32NtPath.UniStr.Length - sizeof(L"System32") + sizeof(WCHAR)
1154 && memcmp(uBuf.UniStr.Buffer, g_System32NtPath.UniStr.Buffer,
1155 g_System32NtPath.UniStr.Length - sizeof(L"System32") + sizeof(WCHAR)) == 0)
1156 {
1157 PRTUTF16 pwszName = &uBuf.UniStr.Buffer[(g_System32NtPath.UniStr.Length - sizeof(L"System32") + sizeof(WCHAR)) / sizeof(WCHAR)];
1158 if (RTUtf16ICmpAscii(pwszName, "WindowsShell.Manifest") == 0)
1159 {
1160 /*
1161 * Drop all executable access to the mapping and let it continue.
1162 */
1163 SUP_DPRINTF(("supR3HardenedScreenImage/%s: Applying the drop-exec-kludge for '%ls'\n", pszCaller, uBuf.UniStr.Buffer));
1164 if (*pfAccess & SECTION_MAP_EXECUTE)
1165 *pfAccess = (*pfAccess & ~SECTION_MAP_EXECUTE) | SECTION_MAP_READ;
1166 if (*pfProtect & PAGE_EXECUTE)
1167 *pfProtect = (*pfProtect & ~PAGE_EXECUTE) | PAGE_READONLY;
1168 *pfProtect = (*pfProtect & ~UINT32_C(0xf0)) | ((*pfProtect & UINT32_C(0xe0)) >> 4);
1169 if (hMyFile != hFile)
1170 NtClose(hMyFile);
1171 *pfCallRealApi = true;
1172 return STATUS_SUCCESS;
1173 }
1174 }
1175
1176#ifndef VBOX_PERMIT_EVEN_MORE
1177 /*
1178 * Check the path. We don't allow DLLs to be loaded from just anywhere:
1179 * 1. System32 - normal code or cat signing, owner TrustedInstaller.
1180 * 2. WinSxS - normal code or cat signing, owner TrustedInstaller.
1181 * 3. VirtualBox - kernel code signing and integrity checks.
1182 * 4. AppPatchDir - normal code or cat signing, owner TrustedInstaller.
1183 * 5. Program Files - normal code or cat signing, owner TrustedInstaller.
1184 * 6. Common Files - normal code or cat signing, owner TrustedInstaller.
1185 * 7. x86 variations of 4 & 5 - ditto.
1186 */
1187 Assert(g_SupLibHardenedExeNtPath.UniStr.Buffer[g_offSupLibHardenedExeNtName - 1] == '\\');
1188 uint32_t fFlags = 0;
1189 if (supHardViUniStrPathStartsWithUniStr(&uBuf.UniStr, &g_System32NtPath.UniStr, true /*fCheckSlash*/))
1190 fFlags |= SUPHNTVI_F_ALLOW_CAT_FILE_VERIFICATION | SUPHNTVI_F_TRUSTED_INSTALLER_OWNER;
1191 else if (supHardViUniStrPathStartsWithUniStr(&uBuf.UniStr, &g_WinSxSNtPath.UniStr, true /*fCheckSlash*/))
1192 fFlags |= SUPHNTVI_F_ALLOW_CAT_FILE_VERIFICATION | SUPHNTVI_F_TRUSTED_INSTALLER_OWNER;
1193 else if (supHardViUtf16PathStartsWithEx(uBuf.UniStr.Buffer, uBuf.UniStr.Length / sizeof(WCHAR),
1194 g_SupLibHardenedExeNtPath.UniStr.Buffer,
1195 g_offSupLibHardenedExeNtName, false /*fCheckSlash*/))
1196 fFlags |= SUPHNTVI_F_REQUIRE_KERNEL_CODE_SIGNING | SUPHNTVI_F_REQUIRE_SIGNATURE_ENFORCEMENT;
1197# ifdef VBOX_PERMIT_MORE
1198 else if (supHardViIsAppPatchDir(uBuf.UniStr.Buffer, uBuf.UniStr.Length / sizeof(WCHAR)))
1199 fFlags |= SUPHNTVI_F_ALLOW_CAT_FILE_VERIFICATION | SUPHNTVI_F_TRUSTED_INSTALLER_OWNER;
1200 else if (supHardViUniStrPathStartsWithUniStr(&uBuf.UniStr, &g_ProgramFilesNtPath.UniStr, true /*fCheckSlash*/))
1201 fFlags |= SUPHNTVI_F_ALLOW_CAT_FILE_VERIFICATION | SUPHNTVI_F_TRUSTED_INSTALLER_OWNER;
1202 else if (supHardViUniStrPathStartsWithUniStr(&uBuf.UniStr, &g_CommonFilesNtPath.UniStr, true /*fCheckSlash*/))
1203 fFlags |= SUPHNTVI_F_ALLOW_CAT_FILE_VERIFICATION | SUPHNTVI_F_TRUSTED_INSTALLER_OWNER;
1204# ifdef RT_ARCH_AMD64
1205 else if (supHardViUniStrPathStartsWithUniStr(&uBuf.UniStr, &g_ProgramFilesX86NtPath.UniStr, true /*fCheckSlash*/))
1206 fFlags |= SUPHNTVI_F_ALLOW_CAT_FILE_VERIFICATION | SUPHNTVI_F_TRUSTED_INSTALLER_OWNER;
1207 else if (supHardViUniStrPathStartsWithUniStr(&uBuf.UniStr, &g_CommonFilesX86NtPath.UniStr, true /*fCheckSlash*/))
1208 fFlags |= SUPHNTVI_F_ALLOW_CAT_FILE_VERIFICATION | SUPHNTVI_F_TRUSTED_INSTALLER_OWNER;
1209# endif
1210# endif
1211# ifdef VBOX_PERMIT_VISUAL_STUDIO_PROFILING
1212 /* Hack to allow profiling our code with Visual Studio. */
1213 else if ( uBuf.UniStr.Length > sizeof(L"\\SamplingRuntime.dll")
1214 && memcmp(uBuf.UniStr.Buffer + (uBuf.UniStr.Length - sizeof(L"\\SamplingRuntime.dll") + sizeof(WCHAR)) / sizeof(WCHAR),
1215 L"\\SamplingRuntime.dll", sizeof(L"\\SamplingRuntime.dll") - sizeof(WCHAR)) == 0 )
1216 {
1217 if (hMyFile != hFile)
1218 NtClose(hMyFile);
1219 *pfCallRealApi = true;
1220 return STATUS_SUCCESS;
1221 }
1222# endif
1223 else
1224 {
1225 supR3HardenedError(VINF_SUCCESS, false,
1226 "supR3HardenedScreenImage/%s: Not a trusted location: '%ls' (fImage=%d fProtect=%#x fAccess=%#x)",
1227 pszCaller, uBuf.UniStr.Buffer, fImage, *pfAccess, *pfProtect);
1228 if (hMyFile != hFile)
1229 NtClose(hMyFile);
1230 return STATUS_TRUST_FAILURE;
1231 }
1232
1233#else /* VBOX_PERMIT_EVEN_MORE */
1234 /*
1235 * Require trusted installer + some kind of signature on everything, except
1236 * for the VBox bits where we require kernel code signing and special
1237 * integrity checks.
1238 */
1239 Assert(g_SupLibHardenedExeNtPath.UniStr.Buffer[g_offSupLibHardenedExeNtName - 1] == '\\');
1240 uint32_t fFlags = 0;
1241 if (supHardViUtf16PathStartsWithEx(uBuf.UniStr.Buffer, uBuf.UniStr.Length / sizeof(WCHAR),
1242 g_SupLibHardenedExeNtPath.UniStr.Buffer,
1243 g_offSupLibHardenedExeNtName, false /*fCheckSlash*/))
1244 fFlags |= SUPHNTVI_F_REQUIRE_KERNEL_CODE_SIGNING | SUPHNTVI_F_REQUIRE_SIGNATURE_ENFORCEMENT;
1245 else
1246 fFlags |= SUPHNTVI_F_ALLOW_CAT_FILE_VERIFICATION | SUPHNTVI_F_TRUSTED_INSTALLER_OWNER;
1247#endif /* VBOX_PERMIT_EVEN_MORE */
1248
1249 /*
1250 * Do the verification. For better error message we borrow what's
1251 * left of the path buffer for an RTERRINFO buffer.
1252 */
1253 RTERRINFO ErrInfo;
1254 RTErrInfoInit(&ErrInfo, (char *)&uBuf.abBuffer[cbNameBuf], sizeof(uBuf) - cbNameBuf);
1255
1256 int rc;
1257 bool fWinVerifyTrust = false;
1258 if (!pCacheHit)
1259 rc = supHardenedWinVerifyImageByHandle(hMyFile, uBuf.UniStr.Buffer, fFlags, &fWinVerifyTrust, &ErrInfo);
1260 else
1261 {
1262 rc = supHardenedWinVerifyImageTrust(hMyFile, uBuf.UniStr.Buffer, fFlags, pCacheHit->rc, &fWinVerifyTrust, &ErrInfo);
1263 SUP_DPRINTF(("supR3HardenedScreenImage/%s: supHardenedWinVerifyImageTrust returns %d (was %d) fWinVerifyTrust=%d\n",
1264 pszCaller, rc, pCacheHit->rc, fWinVerifyTrust));
1265 }
1266 if (RT_FAILURE(rc))
1267 {
1268 supR3HardenedError(VINF_SUCCESS, false,
1269 "supR3HardenedScreenImage/%s: rc=%Rrc fImage=%d fProtect=%#x fAccess=%#x %ls: %s\n",
1270 pszCaller, rc, fImage, *pfAccess, *pfProtect, uBuf.UniStr.Buffer, ErrInfo.pszMsg);
1271 if (hMyFile != hFile)
1272 NtClose(hMyFile);
1273 return STATUS_TRUST_FAILURE;
1274 }
1275
1276 /*
1277 * Insert or update the cache.
1278 */
1279 if (!pCacheHit)
1280 {
1281 if (hMyFile != hFile)
1282 supR3HardenedWinVerifyCacheInsert(&uBuf.UniStr, hMyFile, rc, fWinVerifyTrust);
1283 }
1284 else
1285 {
1286 if (fWinVerifyTrust)
1287 pCacheHit->fWinVerifyTrust = fWinVerifyTrust;
1288 if (hMyFile != hFile)
1289 NtClose(hMyFile);
1290 }
1291
1292 *pfCallRealApi = true;
1293 return STATUS_SUCCESS;
1294}
1295
1296
1297/**
1298 * Preloads a file into the verify cache if possible.
1299 *
1300 * This is used to avoid known cyclic LoadLibrary issues with WinVerifyTrust.
1301 *
1302 * @param pwszName The name of the DLL to verify.
1303 */
1304DECLHIDDEN(void) supR3HardenedWinVerifyCachePreload(PCRTUTF16 pwszName)
1305{
1306 HANDLE hFile = RTNT_INVALID_HANDLE_VALUE;
1307 IO_STATUS_BLOCK Ios = RTNT_IO_STATUS_BLOCK_INITIALIZER;
1308
1309 UNICODE_STRING UniStr;
1310 UniStr.Buffer = (PWCHAR)pwszName;
1311 UniStr.Length = (USHORT)(RTUtf16Len(pwszName) * sizeof(WCHAR));
1312 UniStr.MaximumLength = UniStr.Length + sizeof(WCHAR);
1313
1314 OBJECT_ATTRIBUTES ObjAttr;
1315 InitializeObjectAttributes(&ObjAttr, &UniStr, OBJ_CASE_INSENSITIVE, NULL /*hRootDir*/, NULL /*pSecDesc*/);
1316
1317 NTSTATUS rcNt = NtCreateFile(&hFile,
1318 FILE_READ_DATA | READ_CONTROL | SYNCHRONIZE,
1319 &ObjAttr,
1320 &Ios,
1321 NULL /* Allocation Size*/,
1322 FILE_ATTRIBUTE_NORMAL,
1323 FILE_SHARE_READ,
1324 FILE_OPEN,
1325 FILE_NON_DIRECTORY_FILE | FILE_SYNCHRONOUS_IO_NONALERT,
1326 NULL /*EaBuffer*/,
1327 0 /*EaLength*/);
1328 if (NT_SUCCESS(rcNt))
1329 rcNt = Ios.Status;
1330 if (!NT_SUCCESS(rcNt))
1331 {
1332 SUP_DPRINTF(("supR3HardenedWinVerifyCachePreload: Error %#x opening '%ls'.\n", rcNt, pwszName));
1333 return;
1334 }
1335
1336 ULONG fAccess = 0;
1337 ULONG fProtect = 0;
1338 bool fCallRealApi;
1339 //SUP_DPRINTF(("supR3HardenedWinVerifyCachePreload: scanning %ls\n", pwszName));
1340 supR3HardenedScreenImage(hFile, false, &fAccess, &fProtect, &fCallRealApi, "preload");
1341 //SUP_DPRINTF(("supR3HardenedWinVerifyCachePreload: done %ls\n", pwszName));
1342
1343 NtClose(hFile);
1344}
1345
1346
1347
1348/**
1349 * Hook that monitors NtCreateSection calls.
1350 *
1351 * @returns NT status code.
1352 * @param phSection Where to return the section handle.
1353 * @param fAccess The desired access.
1354 * @param pObjAttribs The object attributes (optional).
1355 * @param pcbSection The section size (optional).
1356 * @param fProtect The max section protection.
1357 * @param fAttribs The section attributes.
1358 * @param hFile The file to create a section from (optional).
1359 */
1360static NTSTATUS NTAPI
1361supR3HardenedMonitor_NtCreateSection(PHANDLE phSection, ACCESS_MASK fAccess, POBJECT_ATTRIBUTES pObjAttribs,
1362 PLARGE_INTEGER pcbSection, ULONG fProtect, ULONG fAttribs, HANDLE hFile)
1363{
1364 if ( hFile != NULL
1365 && hFile != INVALID_HANDLE_VALUE)
1366 {
1367 bool const fImage = RT_BOOL(fAttribs & (SEC_IMAGE | SEC_PROTECTED_IMAGE));
1368 bool const fExecMap = RT_BOOL(fAccess & SECTION_MAP_EXECUTE);
1369 bool const fExecProt = RT_BOOL(fProtect & (PAGE_EXECUTE | PAGE_EXECUTE_READ | PAGE_EXECUTE_WRITECOPY
1370 | PAGE_EXECUTE_READWRITE));
1371 if (fImage || fExecMap || fExecProt)
1372 {
1373 DWORD dwSavedLastError = GetLastError();
1374
1375 bool fCallRealApi;
1376 //SUP_DPRINTF(("supR3HardenedMonitor_NtCreateSection: 1\n"));
1377 NTSTATUS rcNt = supR3HardenedScreenImage(hFile, fImage, &fAccess, &fProtect, &fCallRealApi, "NtCreateSection");
1378 //SUP_DPRINTF(("supR3HardenedMonitor_NtCreateSection: 2 rcNt=%#x fCallRealApi=%#x\n", rcNt, fCallRealApi));
1379
1380 SetLastError(dwSavedLastError);
1381
1382 if (!NT_SUCCESS(rcNt))
1383 return rcNt;
1384 Assert(fCallRealApi);
1385 if (!fCallRealApi)
1386 return STATUS_TRUST_FAILURE;
1387
1388 }
1389 }
1390
1391 /*
1392 * Call checked out OK, call the original.
1393 */
1394 return g_pfnNtCreateSectionReal(phSection, fAccess, pObjAttribs, pcbSection, fProtect, fAttribs, hFile);
1395}
1396
1397
1398/**
1399 * Hooks that intercepts LdrLoadDll calls.
1400 *
1401 * Two purposes:
1402 * -# Enforce our own search path restrictions.
1403 * -# Prevalidate DLLs about to be loaded so we don't upset the loader data
1404 * by doing it from within the NtCreateSection hook (WinVerifyTrust
1405 * seems to be doing harm there on W7/32).
1406 *
1407 * @returns
1408 * @param pwszSearchPath The search path to use.
1409 * @param pfFlags Flags on input. DLL characteristics or something
1410 * on return?
1411 * @param pName The name of the module.
1412 * @param phMod Where the handle of the loaded DLL is to be
1413 * returned to the caller.
1414 */
1415static NTSTATUS NTAPI
1416supR3HardenedMonitor_LdrLoadDll(PWSTR pwszSearchPath, PULONG pfFlags, PUNICODE_STRING pName, PHANDLE phMod)
1417{
1418 DWORD dwSavedLastError = GetLastError();
1419
1420 /*
1421 * Reject things we don't want to deal with.
1422 */
1423 if (!pName || pName->Length == 0)
1424 {
1425 supR3HardenedError(VINF_SUCCESS, false, "supR3HardenedMonitor_LdrLoadDll: name is NULL or have a zero length.\n");
1426 return STATUS_INVALID_PARAMETER;
1427 }
1428 SUP_DPRINTF(("supR3HardenedMonitor_LdrLoadDll: pName=%.*ls *pfFlags=%#x pwszSearchPath=%ls\n",
1429 (unsigned)pName->Length / sizeof(WCHAR), pName->Buffer, pfFlags ? *pfFlags : UINT32_MAX, pwszSearchPath));
1430
1431 /*
1432 * Reject long paths that's close to the 260 limit without looking.
1433 */
1434 if (pName->Length > 256 * sizeof(WCHAR))
1435 {
1436 supR3HardenedError(VINF_SUCCESS, false, "supR3HardenedMonitor_LdrLoadDll: too long name: %#x bytes\n", pName->Length);
1437 return STATUS_NAME_TOO_LONG;
1438 }
1439
1440 /*
1441 * Absolute path?
1442 */
1443 bool fSkipValidation = false;
1444 WCHAR wszPath[260];
1445 UNICODE_STRING ResolvedName;
1446 if ( ( pName->Length >= 4 * sizeof(WCHAR)
1447 && RT_C_IS_ALPHA(pName->Buffer[0])
1448 && pName->Buffer[1] == ':'
1449 && RTPATH_IS_SLASH(pName->Buffer[2]) )
1450 || ( pName->Length >= 1 * sizeof(WCHAR)
1451 && RTPATH_IS_SLASH(pName->Buffer[1]) )
1452 )
1453 {
1454 memcpy(wszPath, pName->Buffer, pName->Length);
1455 wszPath[pName->Length / sizeof(WCHAR)] = '\0';
1456 }
1457 /*
1458 * Not an absolute path. Check if it's one of those special API set DLLs.
1459 */
1460 else if (supHardViUtf16PathStartsWithEx(pName->Buffer, pName->Length / sizeof(WCHAR),
1461 L"api-ms-win-", 11, false /*fCheckSlash*/))
1462 {
1463 memcpy(wszPath, pName->Buffer, pName->Length);
1464 wszPath[pName->Length / sizeof(WCHAR)] = '\0';
1465 fSkipValidation = true;
1466 }
1467 /*
1468 * Not an absolute path or special API set. There are two alternatives
1469 * now, either there is no path at all or there is a relative path. We
1470 * will resolve it to an absolute path in either case, failing the call
1471 * if we can't. If there isn't a path.
1472 */
1473 else
1474 {
1475 PCWCHAR pawcName = pName->Buffer;
1476 uint32_t cwcName = pName->Length / sizeof(WCHAR);
1477 uint32_t offLastSlash = UINT32_MAX;
1478 uint32_t offLastDot = UINT32_MAX;
1479 for (uint32_t i = 0; i < cwcName; i++)
1480 switch (pawcName[i])
1481 {
1482 case '\\':
1483 case '/':
1484 offLastSlash = i;
1485 offLastDot = UINT32_MAX;
1486 break;
1487 case '.':
1488 offLastDot = i;
1489 break;
1490 }
1491
1492 bool const fNeedDllSuffix = offLastDot == UINT32_MAX && offLastSlash == UINT32_MAX;
1493
1494 if (offLastDot != UINT32_MAX && offLastDot == cwcName - 1)
1495 cwcName--;
1496
1497 /*
1498 * Reject relative paths for now as they might be breakout attempts.
1499 */
1500 if (offLastSlash != UINT32_MAX)
1501 {
1502 supR3HardenedError(VINF_SUCCESS, false,
1503 "supR3HardenedMonitor_LdrLoadDll: relative name not permitted: %.*ls\n",
1504 cwcName, pawcName);
1505 return STATUS_OBJECT_NAME_INVALID;
1506 }
1507
1508 /*
1509 * Search for the DLL. Only System32 is allowed as the target of
1510 * a search on the API level, all VBox calls will have full paths.
1511 */
1512 UINT cwc = GetSystemDirectoryW(wszPath, RT_ELEMENTS(wszPath) - 32);
1513 if (!cwc)
1514 {
1515 supR3HardenedError(VINF_SUCCESS, false,
1516 "supR3HardenedMonitor_LdrLoadDll: GetSystemDirectoryW failed: %u\n", GetLastError());
1517 return STATUS_UNEXPECTED_IO_ERROR;
1518 }
1519 if (cwc + 1 + cwcName + fNeedDllSuffix * 4 >= RT_ELEMENTS(wszPath))
1520 {
1521 supR3HardenedError(VINF_SUCCESS, false,
1522 "supR3HardenedMonitor_LdrLoadDll: Name too long (system32): %.*ls\n", cwcName, pawcName);
1523 return STATUS_NAME_TOO_LONG;
1524 }
1525 wszPath[cwc++] = '\\';
1526 memcpy(&wszPath[cwc], pawcName, cwcName * sizeof(WCHAR));
1527 cwc += cwcName;
1528 if (!fNeedDllSuffix)
1529 wszPath[cwc] = '\0';
1530 else
1531 {
1532 memcpy(&wszPath[cwc], L".dll", 5 * sizeof(WCHAR));
1533 cwc += 4;
1534 }
1535
1536 ResolvedName.Buffer = wszPath;
1537 ResolvedName.Length = (USHORT)(cwc * sizeof(WCHAR));
1538 ResolvedName.MaximumLength = ResolvedName.Length + sizeof(WCHAR);
1539
1540 SUP_DPRINTF(("supR3HardenedMonitor_LdrLoadDll: '%.*ls' -> '%.*ls'\n",
1541 (unsigned)pName->Length / sizeof(WCHAR), pName->Buffer,
1542 ResolvedName.Length / sizeof(WCHAR), ResolvedName.Buffer));
1543 pName = &ResolvedName;
1544 }
1545
1546 NTSTATUS rcNt;
1547 if (!fSkipValidation)
1548 {
1549 /*
1550 * Try open the file. If this fails, never mind, just pass it on to
1551 * the real API as we've replaced any searchable name with a full name
1552 * and the real API can come up with a fitting status code for it.
1553 */
1554 HANDLE hFile = CreateFileW(wszPath, GENERIC_READ, FILE_SHARE_READ, NULL /*pSecurityAttributes*/,
1555 OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL /*hTemplateFile*/);
1556 if (hFile != INVALID_HANDLE_VALUE)
1557 {
1558 ULONG fAccess = 0;
1559 ULONG fProtect = 0;
1560 bool fCallRealApi = false;
1561 rcNt = supR3HardenedScreenImage(hFile, true /*fImage*/, &fAccess, &fProtect, &fCallRealApi, "LdrLoadDll");
1562 NtClose(hFile);
1563 if (!NT_SUCCESS(rcNt))
1564 {
1565 supR3HardenedError(VINF_SUCCESS, false, "supR3HardenedMonitor_LdrLoadDll: rejecting '%ls': rcNt=%#x", wszPath, rcNt);
1566 return rcNt;
1567 }
1568
1569 supR3HardenedWinVerifyCacheProcessImportTodos();
1570 }
1571 else
1572 {
1573 DWORD dwErr = GetLastError();
1574 SUP_DPRINTF(("supR3HardenedMonitor_LdrLoadDll: error opening '%ls': %u\n", wszPath, dwErr));
1575 }
1576 }
1577
1578 /*
1579 * Screened successfully enough. Call the real thing.
1580 */
1581 SetLastError(dwSavedLastError);
1582 rcNt = g_pfnLdrLoadDllReal(pwszSearchPath, pfFlags, pName, phMod);
1583
1584 /* Log the result. */
1585 dwSavedLastError = GetLastError();
1586 if (NT_SUCCESS(rcNt) && phMod)
1587 SUP_DPRINTF(("supR3HardenedMonitor_LdrLoadDll: returns rcNt=%#x hMod=%p '%ls'\n", rcNt, *phMod, wszPath));
1588 else
1589 SUP_DPRINTF(("supR3HardenedMonitor_LdrLoadDll: returns rcNt=%#x '%ls'\n", rcNt, wszPath));
1590 SetLastError(dwSavedLastError);
1591
1592 return rcNt;
1593}
1594
1595
1596
1597#ifdef RT_ARCH_AMD64
1598/**
1599 * Tries to allocate memory between @a uStart and @a uEnd.
1600 *
1601 * @returns Pointer to the memory on success. NULL on failure.
1602 * @param uStart The start address.
1603 * @param uEnd The end address. This is lower than @a uStart
1604 * if @a iDirection is negative, and higher if
1605 * positive.
1606 * @param iDirection The search direction.
1607 * @param cbAlloc The number of bytes to allocate.
1608 */
1609static void *supR3HardenedWinAllocHookMemory(uintptr_t uStart, uintptr_t uEnd, intptr_t iDirection, size_t cbAlloc)
1610{
1611 size_t const cbAllocGranularity = _64K;
1612 size_t const uAllocGranularityMask = ~(cbAllocGranularity - 1);
1613 HANDLE const hProc = NtCurrentProcess();
1614
1615 /*
1616 * Make uEnd the last valid return address.
1617 */
1618 if (iDirection > 0)
1619 {
1620 SUPR3HARDENED_ASSERT(uEnd > cbAlloc);
1621 uEnd -= cbAlloc;
1622 uEnd &= uAllocGranularityMask;
1623 }
1624 else
1625 uEnd = RT_ALIGN_Z(uEnd, cbAllocGranularity);
1626
1627 /*
1628 * Search for free memory.
1629 */
1630 uintptr_t uCur = uStart & uAllocGranularityMask;
1631 for (;;)
1632 {
1633 /*
1634 * Examine the memory at this address, if it's free, try make the allocation here.
1635 */
1636 SIZE_T cbIgn;
1637 MEMORY_BASIC_INFORMATION MemInfo;
1638 SUPR3HARDENED_ASSERT_NT_SUCCESS(NtQueryVirtualMemory(hProc,
1639 (void *)uCur,
1640 MemoryBasicInformation,
1641 &MemInfo,
1642 sizeof(MemInfo),
1643 &cbIgn));
1644 if ( MemInfo.State == MEM_FREE
1645 && MemInfo.RegionSize >= cbAlloc)
1646 {
1647 for (;;)
1648 {
1649 SUPR3HARDENED_ASSERT((uintptr_t)MemInfo.BaseAddress <= uCur);
1650
1651 PVOID pvMem = (PVOID)uCur;
1652 SIZE_T cbMem = cbAlloc;
1653 NTSTATUS rcNt = NtAllocateVirtualMemory(hProc, &pvMem, 0 /*ZeroBits*/, &cbAlloc,
1654 MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
1655 if (NT_SUCCESS(rcNt))
1656 {
1657 if ( iDirection > 0
1658 ? (uintptr_t)pvMem >= uStart
1659 && (uintptr_t)pvMem <= uEnd
1660 : (uintptr_t)pvMem >= uEnd
1661 && (uintptr_t)pvMem <= uStart)
1662 return pvMem;
1663 NtFreeVirtualMemory(hProc, &pvMem, &cbMem, MEM_RELEASE);
1664 }
1665
1666 /* Advance within the free area and try again? */
1667 uintptr_t uNext = iDirection > 0 ? uCur + cbAllocGranularity : uCur - cbAllocGranularity;
1668 uNext &= uAllocGranularityMask;
1669 if ( iDirection > 0
1670 ? uNext <= uCur
1671 || uNext > uEnd
1672 || uNext - (uintptr_t)MemInfo.BaseAddress > MemInfo.RegionSize
1673 || MemInfo.RegionSize - (uNext - (uintptr_t)MemInfo.BaseAddress) < cbAlloc
1674 : uNext >= uCur
1675 || uNext < uEnd
1676 || uNext < (uintptr_t)MemInfo.BaseAddress)
1677 break;
1678 uCur = uNext;
1679 }
1680 }
1681
1682 /*
1683 * Advance to the next memory region.
1684 */
1685 if (iDirection > 0)
1686 {
1687 uCur = (uintptr_t)MemInfo.BaseAddress + MemInfo.RegionSize;
1688 uCur = RT_ALIGN_Z(uCur, cbAllocGranularity);
1689 if (uCur >= uEnd)
1690 break;
1691 }
1692 else
1693 {
1694 uCur = (uintptr_t)(MemInfo.AllocationBase ? MemInfo.AllocationBase : MemInfo.BaseAddress);
1695 if (uCur > uEnd)
1696 uCur -= cbAlloc;
1697 uCur &= uAllocGranularityMask;
1698 if (uCur < uEnd)
1699 break;
1700 }
1701 }
1702 return NULL;
1703}
1704#endif
1705
1706
1707static void supR3HardenedWinHookFailed(const char *pszWhich, uint8_t const *pbPrologue)
1708{
1709 supR3HardenedFatalMsg("supR3HardenedWinInstallHooks", kSupInitOp_Misc, VERR_NO_MEMORY,
1710 "Failed to install %s monitor: %x %x %x %x %x %x %x %x %x %x %x %x %x %x %x %x\n "
1711#ifdef RT_ARCH_X86
1712 "(It is also possible you are running 32-bit VirtualBox under 64-bit windows.)\n"
1713#endif
1714 ,
1715 pszWhich,
1716 pbPrologue[0], pbPrologue[1], pbPrologue[2], pbPrologue[3],
1717 pbPrologue[4], pbPrologue[5], pbPrologue[6], pbPrologue[7],
1718 pbPrologue[8], pbPrologue[9], pbPrologue[10], pbPrologue[11],
1719 pbPrologue[12], pbPrologue[13], pbPrologue[14], pbPrologue[15]);
1720}
1721
1722
1723/**
1724 * Install hooks for intercepting calls dealing with mapping shared libraries
1725 * into the process.
1726 *
1727 * This allows us to prevent undesirable shared libraries from being loaded.
1728 *
1729 * @remarks We assume we're alone in this process, so no seralizing trickery is
1730 * necessary when installing the patch.
1731 *
1732 * @remarks We would normally just copy the prologue sequence somewhere and add
1733 * a jump back at the end of it. But because we wish to avoid
1734 * allocating executable memory, we need to have preprepared assembly
1735 * "copies". This makes the non-system call patching a little tedious
1736 * and inflexible.
1737 */
1738DECLHIDDEN(void) supR3HardenedWinInstallHooks(void)
1739{
1740 NTSTATUS rcNt;
1741
1742#ifndef VBOX_WITHOUT_DEBUGGER_CHECKS
1743 /*
1744 * Install a anti debugging hack before we continue. This prevents most
1745 * notifications from ending up in the debugger. (Also applied to the
1746 * child process when respawning.)
1747 */
1748 rcNt = NtSetInformationThread(NtCurrentThread(), ThreadHideFromDebugger, NULL, 0);
1749 if (!NT_SUCCESS(rcNt))
1750 supR3HardenedFatalMsg("supR3HardenedWinInstallHooks", kSupInitOp_Misc, VERR_GENERAL_FAILURE,
1751 "NtSetInformationThread/ThreadHideFromDebugger failed: %#x\n", rcNt);
1752#endif
1753
1754 /*
1755 * Disable hard error popups so we can quietly refuse images to be loaded.
1756 */
1757 ULONG fHardErr = 0;
1758 rcNt = NtQueryInformationProcess(NtCurrentProcess(), ProcessDefaultHardErrorMode, &fHardErr, sizeof(fHardErr), NULL);
1759 if (!NT_SUCCESS(rcNt))
1760 supR3HardenedFatalMsg("supR3HardenedWinInstallHooks", kSupInitOp_Misc, VERR_GENERAL_FAILURE,
1761 "NtQueryInformationProcess/ProcessDefaultHardErrorMode failed: %#x\n", rcNt);
1762 if (fHardErr & PROCESS_HARDERR_CRITICAL_ERROR)
1763 {
1764 fHardErr &= ~PROCESS_HARDERR_CRITICAL_ERROR;
1765 rcNt = NtSetInformationProcess(NtCurrentProcess(), ProcessDefaultHardErrorMode, &fHardErr, sizeof(fHardErr));
1766 if (!NT_SUCCESS(rcNt))
1767 supR3HardenedFatalMsg("supR3HardenedWinInstallHooks", kSupInitOp_Misc, VERR_GENERAL_FAILURE,
1768 "NtSetInformationProcess/ProcessDefaultHardErrorMode failed: %#x\n", rcNt);
1769 }
1770
1771 /*
1772 * Locate the routines first so we can allocate memory that's near enough.
1773 */
1774 HMODULE hmodNtDll = GetModuleHandleW(L"NTDLL");
1775 SUPR3HARDENED_ASSERT(hmodNtDll != NULL);
1776
1777 FARPROC pfnNtCreateSection = GetProcAddress(hmodNtDll, "NtCreateSection");
1778 SUPR3HARDENED_ASSERT(pfnNtCreateSection != NULL);
1779 //SUPR3HARDENED_ASSERT(pfnNtCreateSection == (FARPROC)NtCreateSection);
1780
1781 FARPROC pfnLdrLoadDll = GetProcAddress(hmodNtDll, "LdrLoadDll");
1782 SUPR3HARDENED_ASSERT(pfnLdrLoadDll != NULL);
1783 //SUPR3HARDENED_ASSERT(pfnLdrLoadDll == (FARPROC)LdrLoadDll);
1784
1785
1786#ifdef RT_ARCH_AMD64
1787 /*
1788 * For 64-bit hosts we need some memory within a +/-2GB range of the
1789 * actual function to be able to patch it.
1790 */
1791 uintptr_t uStart = RT_MAX((uintptr_t)pfnNtCreateSection, (uintptr_t)pfnLdrLoadDll);
1792 size_t cbMem = _4K;
1793 void *pvMem = supR3HardenedWinAllocHookMemory(uStart, uStart - _2G + PAGE_SIZE, -1, cbMem);
1794 if (!pvMem)
1795 {
1796 uintptr_t uStart = RT_MIN((uintptr_t)pfnNtCreateSection, (uintptr_t)pfnLdrLoadDll);
1797 pvMem = supR3HardenedWinAllocHookMemory(uStart, uStart + _2G - PAGE_SIZE, 1, cbMem);
1798 if (!pvMem)
1799 supR3HardenedFatalMsg("supR3HardenedWinInstallHooks", kSupInitOp_Misc, VERR_NO_MEMORY,
1800 "Failed to allocate memory within the +/-2GB range from NTDLL.\n");
1801 }
1802 uintptr_t *puJmpTab = (uintptr_t *)pvMem;
1803#endif
1804
1805 /*
1806 * Hook #1 - NtCreateSection.
1807 * Purpose: Validate everything that can be mapped into the process before
1808 * it's mapped and we still have a file handle to work with.
1809 */
1810 uint8_t * const pbNtCreateSection = (uint8_t *)(uintptr_t)pfnNtCreateSection;
1811
1812#ifdef RT_ARCH_AMD64
1813 /*
1814 * Patch 64-bit hosts.
1815 */
1816 PFNRT pfnCallReal = NULL;
1817 uint8_t offJmpBack = UINT8_MAX;
1818
1819 /* Pattern #1: XP64/W2K3-64 thru Windows 8.1
1820 0:000> u ntdll!NtCreateSection
1821 ntdll!NtCreateSection:
1822 00000000`779f1750 4c8bd1 mov r10,rcx
1823 00000000`779f1753 b847000000 mov eax,47h
1824 00000000`779f1758 0f05 syscall
1825 00000000`779f175a c3 ret
1826 00000000`779f175b 0f1f440000 nop dword ptr [rax+rax]
1827 The variant is the value loaded into eax: W2K3=??, Vista=47h?, W7=47h, W80=48h, W81=49h */
1828 if ( pbNtCreateSection[ 0] == 0x4c /* mov r10, rcx */
1829 && pbNtCreateSection[ 1] == 0x8b
1830 && pbNtCreateSection[ 2] == 0xd1
1831 && pbNtCreateSection[ 3] == 0xb8 /* mov eax, 000000xxh */
1832 && pbNtCreateSection[ 5] == 0x00
1833 && pbNtCreateSection[ 6] == 0x00
1834 && pbNtCreateSection[ 7] == 0x00
1835 && pbNtCreateSection[ 8] == 0x0f /* syscall */
1836 && pbNtCreateSection[ 9] == 0x05
1837 && pbNtCreateSection[10] == 0xc3 /* ret */
1838
1839/* b8 22 35 ed 0 48 63 c0 ff e0 c3 f 1f 44 0 0 - necros2 - agnitum firewall? */
1840 )
1841 {
1842 offJmpBack = 8; /* the 3rd instruction (syscall). */
1843 switch (pbNtCreateSection[4])
1844 {
1845# define SYSCALL(a_Num) case a_Num: pfnCallReal = RT_CONCAT(supR3HardenedJmpBack_NtCreateSection_,a_Num); break;
1846# include "NtCreateSection-template-amd64-syscall-type-1.h"
1847# undef SYSCALL
1848 }
1849 }
1850 if (!pfnCallReal)
1851 supR3HardenedWinHookFailed("NtCreateSection", pbNtCreateSection);
1852
1853 g_pfnNtCreateSectionJmpBack = (PFNRT)(uintptr_t)(pbNtCreateSection + offJmpBack);
1854 *(PFNRT *)&g_pfnNtCreateSectionReal = pfnCallReal;
1855 *puJmpTab = (uintptr_t)supR3HardenedMonitor_NtCreateSection;
1856
1857 DWORD dwOldProt;
1858 SUPR3HARDENED_ASSERT_WIN32_SUCCESS(VirtualProtectEx(NtCurrentProcess(), pbNtCreateSection, 16,
1859 PAGE_EXECUTE_READWRITE, &dwOldProt));
1860 pbNtCreateSection[0] = 0xff;
1861 pbNtCreateSection[1] = 0x25;
1862 *(uint32_t *)&pbNtCreateSection[2] = (uint32_t)((uintptr_t)puJmpTab - (uintptr_t)&pbNtCreateSection[2+4]);
1863
1864 SUPR3HARDENED_ASSERT_WIN32_SUCCESS(VirtualProtectEx(NtCurrentProcess(), pbNtCreateSection, 16,
1865 PAGE_EXECUTE_READ, &dwOldProt));
1866 puJmpTab++;
1867
1868#else
1869 /*
1870 * Patch 32-bit hosts.
1871 */
1872 PFNRT pfnCallReal = NULL;
1873 uint8_t offJmpBack = UINT8_MAX;
1874
1875 /* Pattern #1: XP thru Windows 7
1876 kd> u ntdll!NtCreateSection
1877 ntdll!NtCreateSection:
1878 7c90d160 b832000000 mov eax,32h
1879 7c90d165 ba0003fe7f mov edx,offset SharedUserData!SystemCallStub (7ffe0300)
1880 7c90d16a ff12 call dword ptr [edx]
1881 7c90d16c c21c00 ret 1Ch
1882 7c90d16f 90 nop
1883 The variable bit is the value loaded into eax: XP=32h, W2K3=34h, Vista=4bh, W7=54h
1884
1885 Pattern #2: Windows 8.1
1886 0:000:x86> u ntdll_6a0f0000!NtCreateSection
1887 ntdll_6a0f0000!NtCreateSection:
1888 6a15eabc b854010000 mov eax,154h
1889 6a15eac1 e803000000 call ntdll_6a0f0000!NtCreateSection+0xd (6a15eac9)
1890 6a15eac6 c21c00 ret 1Ch
1891 6a15eac9 8bd4 mov edx,esp
1892 6a15eacb 0f34 sysenter
1893 6a15eacd c3 ret
1894 The variable bit is the value loaded into eax: W81=154h
1895 Note! One nice thing here is that we can share code pattern #1. */
1896
1897 if ( pbNtCreateSection[ 0] == 0xb8 /* mov eax, 000000xxh*/
1898 && pbNtCreateSection[ 2] <= 0x02
1899 && pbNtCreateSection[ 3] == 0x00
1900 && pbNtCreateSection[ 4] == 0x00
1901 && ( ( pbNtCreateSection[ 5] == 0xba /* mov edx, offset SharedUserData!SystemCallStub */
1902 && pbNtCreateSection[ 6] == 0x00
1903 && pbNtCreateSection[ 7] == 0x03
1904 && pbNtCreateSection[ 8] == 0xfe
1905 && pbNtCreateSection[ 9] == 0x7f
1906 && pbNtCreateSection[10] == 0xff /* call [edx] */
1907 && pbNtCreateSection[11] == 0x12
1908 && pbNtCreateSection[12] == 0xc2 /* ret 1ch */
1909 && pbNtCreateSection[13] == 0x1c
1910 && pbNtCreateSection[14] == 0x00)
1911
1912 || ( pbNtCreateSection[ 5] == 0xe8 /* call [$+3] */
1913 && RT_ABS(*(int32_t *)&pbNtCreateSection[6]) < 0x10
1914 && pbNtCreateSection[10] == 0xc2 /* ret 1ch */
1915 && pbNtCreateSection[11] == 0x1c
1916 && pbNtCreateSection[12] == 0x00 )
1917 )
1918 )
1919 {
1920 offJmpBack = 5; /* the 2nd instruction. */
1921 switch (*(uint32_t const *)&pbNtCreateSection[1])
1922 {
1923# define SYSCALL(a_Num) case a_Num: pfnCallReal = RT_CONCAT(supR3HardenedJmpBack_NtCreateSection_,a_Num); break;
1924# include "NtCreateSection-template-x86-syscall-type-1.h"
1925# undef SYSCALL
1926 }
1927 }
1928 if (!pfnCallReal)
1929 supR3HardenedWinHookFailed("NtCreateSection", pbNtCreateSection);
1930
1931 g_pfnNtCreateSectionJmpBack = (PFNRT)(uintptr_t)(pbNtCreateSection + offJmpBack);
1932 *(PFNRT *)&g_pfnNtCreateSectionReal = pfnCallReal;
1933
1934 DWORD dwOldProt;
1935 SUPR3HARDENED_ASSERT_WIN32_SUCCESS(VirtualProtectEx(NtCurrentProcess(), pbNtCreateSection, 16,
1936 PAGE_EXECUTE_READWRITE, &dwOldProt));
1937 pbNtCreateSection[0] = 0xe9;
1938 *(uint32_t *)&pbNtCreateSection[1] = (uintptr_t)supR3HardenedMonitor_NtCreateSection
1939 - (uintptr_t)&pbNtCreateSection[1+4];
1940
1941 SUPR3HARDENED_ASSERT_WIN32_SUCCESS(VirtualProtectEx(NtCurrentProcess(), pbNtCreateSection, 16,
1942 PAGE_EXECUTE_READ, &dwOldProt));
1943#endif
1944
1945 /*
1946 * Hook #2 - LdrLoadDll
1947 * Purpose: (a) Enforce LdrLoadDll search path constraints, and (b) pre-validate
1948 * DLLs so we can avoid calling WinVerifyTrust from the first hook,
1949 * and thus avoiding messing up the loader data on some installations.
1950 *
1951 * This differs from the above function in that is no a system call and
1952 * we're at the mercy of the compiler.
1953 */
1954 uint8_t * const pbLdrLoadDll = (uint8_t *)(uintptr_t)pfnLdrLoadDll;
1955 uint32_t offExecPage = 0;
1956 memset(g_abSupHardReadWriteExecPage, 0xcc, PAGE_SIZE);
1957
1958#ifdef RT_ARCH_AMD64
1959 /*
1960 * Patch 64-bit hosts.
1961 */
1962# if 0
1963 /* Pattern #1:
1964 Windows 8.1:
1965 0:000> u ntdll!LdrLoadDll
1966 ntdll!LdrLoadDll:
1967 00007ffa`814ccd44 488bc4 mov rax,rsp
1968 00007ffa`814ccd47 48895808 mov qword ptr [rax+8],rbx
1969 00007ffa`814ccd4b 48896810 mov qword ptr [rax+10h],rbp
1970 00007ffa`814ccd4f 48897018 mov qword ptr [rax+18h],rsi
1971 00007ffa`814ccd53 48897820 mov qword ptr [rax+20h],rdi
1972 00007ffa`814ccd57 4156 push r14
1973 00007ffa`814ccd59 4883ec70 sub rsp,70h
1974 00007ffa`814ccd5d f6059cd2100009 test byte ptr [ntdll!LdrpDebugFlags (00007ffa`815da000)],9
1975 */
1976 if ( pbLdrLoadDll[0] == 0x48 /* mov rax,rsp */
1977 && pbLdrLoadDll[1] == 0x8b
1978 && pbLdrLoadDll[2] == 0xc4
1979 && pbLdrLoadDll[3] == 0x48 /* mov qword ptr [rax+8],rbx */
1980 && pbLdrLoadDll[4] == 0x89
1981 && pbLdrLoadDll[5] == 0x58
1982 && pbLdrLoadDll[6] == 0x08)
1983 {
1984 offJmpBack = 7; /* the 3rd instruction. */
1985 pfnCallReal = supR3HardenedJmpBack_LdrLoadDll_Type1;
1986 }
1987 /*
1988 Pattern #2:
1989 Windows 8.0:
1990 0:000> u ntdll_w8_64!LdrLoadDll
1991 ntdll_w8_64!LdrLoadDll:
1992 00007ffa`52ffa7c0 48895c2408 mov qword ptr [rsp+8],rbx
1993 00007ffa`52ffa7c5 4889742410 mov qword ptr [rsp+10h],rsi
1994 00007ffa`52ffa7ca 48897c2418 mov qword ptr [rsp+18h],rdi
1995 00007ffa`52ffa7cf 55 push rbp
1996 00007ffa`52ffa7d0 4156 push r14
1997 00007ffa`52ffa7d2 4157 push r15
1998 00007ffa`52ffa7d4 488bec mov rbp,rsp
1999 00007ffa`52ffa7d7 4883ec60 sub rsp,60h
2000 00007ffa`52ffa7db 8b05df321000 mov eax,dword ptr [ntdll_w8_64!LdrpDebugFlags (00007ffa`530fdac0)]
2001 00007ffa`52ffa7e1 4d8bf1 mov r14,r9
2002
2003 */
2004 else if ( pbLdrLoadDll[0] == 0x48 /* mov qword ptr [rsp+8],rbx */
2005 && pbLdrLoadDll[1] == 0x89
2006 && pbLdrLoadDll[2] == 0x5c
2007 && pbLdrLoadDll[3] == 0x24
2008 && pbLdrLoadDll[4] == 0x08
2009 && pbLdrLoadDll[5] == 0x48 /* mov qword ptr [rsp+10h],rsi */
2010 && pbLdrLoadDll[6] == 0x89
2011 && pbLdrLoadDll[7] == 0x74
2012 && pbLdrLoadDll[8] == 0x24
2013 && pbLdrLoadDll[9] == 0x10)
2014 {
2015 offJmpBack = 10; /* the 3rd instruction. */
2016 pfnCallReal = supR3HardenedJmpBack_LdrLoadDll_Type2;
2017 }
2018 /*
2019 Pattern #3:
2020 Windows 7:
2021 ntdll_w7_64!LdrLoadDll:
2022 00000000`58be4a20 48895c2410 mov qword ptr [rsp+10h],rbx
2023 00000000`58be4a25 48896c2418 mov qword ptr [rsp+18h],rbp
2024 00000000`58be4a2a 56 push rsi
2025 00000000`58be4a2b 57 push rdi
2026 00000000`58be4a2c 4154 push r12
2027 00000000`58be4a2e 4883ec50 sub rsp,50h
2028 00000000`58be4a32 f605976e100009 test byte ptr [ntdll_w7_64!ShowSnaps (00000000`58ceb8d0)],9
2029 00000000`58be4a39 498bf1 mov rsi,r9
2030
2031 */
2032 else if ( pbLdrLoadDll[0] == 0x48 /* mov qword ptr [rsp+10h],rbx */
2033 && pbLdrLoadDll[1] == 0x89
2034 && pbLdrLoadDll[2] == 0x5c
2035 && pbLdrLoadDll[3] == 0x24
2036 && pbLdrLoadDll[4] == 0x10)
2037 {
2038 offJmpBack = 5; /* the 2nd instruction. */
2039 pfnCallReal = supR3HardenedJmpBack_LdrLoadDll_Type3;
2040 }
2041 /*
2042 Pattern #4:
2043 Windows Vista:
2044 0:000> u ntdll_vista_64!LdrLoadDll
2045 ntdll_vista_64!LdrLoadDll:
2046 00000000`58c11f60 fff3 push rbx
2047 00000000`58c11f62 56 push rsi
2048 00000000`58c11f63 57 push rdi
2049 00000000`58c11f64 4154 push r12
2050 00000000`58c11f66 4155 push r13
2051 00000000`58c11f68 4156 push r14
2052 00000000`58c11f6a 4157 push r15
2053 00000000`58c11f6c 4881ecb0020000 sub rsp,2B0h
2054 00000000`58c11f73 488b05367b0e00 mov rax,qword ptr [ntdll_vista_64!_security_cookie (00000000`58cf9ab0)]
2055 00000000`58c11f7a 4833c4 xor rax,rsp
2056 00000000`58c11f7d 48898424a0020000 mov qword ptr [rsp+2A0h],rax
2057
2058 */
2059 else if ( pbLdrLoadDll[0] == 0xff /* push rbx */
2060 && pbLdrLoadDll[1] == 0xf3
2061 && pbLdrLoadDll[2] == 0x56 /* push rsi */
2062 && pbLdrLoadDll[3] == 0x57 /* push rdi */
2063 && pbLdrLoadDll[4] == 0x41 /* push r12 */
2064 && pbLdrLoadDll[5] == 0x54)
2065 {
2066 offJmpBack = 6; /* the 5th instruction. */
2067 pfnCallReal = supR3HardenedJmpBack_LdrLoadDll_Type4;
2068 }
2069 /*
2070 Pattern #5:
2071 Windows XP64:
2072 0:000> u ntdll!LdrLoadDll
2073 ntdll!LdrLoadDll:
2074 00000000`78efa580 4c8bdc mov r11,rsp
2075 00000000`78efa583 4881ece8020000 sub rsp,2E8h
2076 00000000`78efa58a 49895bf8 mov qword ptr [r11-8],rbx
2077 00000000`78efa58e 498973f0 mov qword ptr [r11-10h],rsi
2078 00000000`78efa592 49897be8 mov qword ptr [r11-18h],rdi
2079 00000000`78efa596 4d8963e0 mov qword ptr [r11-20h],r12
2080 00000000`78efa59a 4d896bd8 mov qword ptr [r11-28h],r13
2081 00000000`78efa59e 4d8973d0 mov qword ptr [r11-30h],r14
2082 00000000`78efa5a2 4d897bc8 mov qword ptr [r11-38h],r15
2083 00000000`78efa5a6 488b051bd10a00 mov rax,qword ptr [ntdll!_security_cookie (00000000`78fa76c8)]
2084 00000000`78efa5ad 48898424a0020000 mov qword ptr [rsp+2A0h],rax
2085 00000000`78efa5b5 4d8bf9 mov r15,r9
2086 00000000`78efa5b8 4c8bf2 mov r14,rdx
2087 00000000`78efa5bb 4c8be9 mov r13,rcx
2088 00000000`78efa5be 4c89442458 mov qword ptr [rsp+58h],r8
2089 00000000`78efa5c3 66c74424680000 mov word ptr [rsp+68h],0
2090
2091 */
2092 else if ( pbLdrLoadDll[0] == 0x4c /* mov r11,rsp */
2093 && pbLdrLoadDll[1] == 0x8b
2094 && pbLdrLoadDll[2] == 0xdc
2095 && pbLdrLoadDll[3] == 0x48 /* sub rsp,2e8h */
2096 && pbLdrLoadDll[4] == 0x81
2097 && pbLdrLoadDll[5] == 0xec
2098 && pbLdrLoadDll[6] == 0xe8
2099 && pbLdrLoadDll[7] == 0x02
2100 && pbLdrLoadDll[8] == 0x00
2101 && pbLdrLoadDll[9] == 0x00)
2102 {
2103 offJmpBack = 10; /* the 3rd instruction. */
2104 pfnCallReal = supR3HardenedJmpBack_LdrLoadDll_Type5;
2105 }
2106 else
2107 supR3HardenedWinHookFailed("LdrLoadDll", pbLdrLoadDll);
2108# else
2109 /* Just use the disassembler to skip 6 bytes or more. */
2110 DISSTATE Dis;
2111 uint32_t cbInstr;
2112 offJmpBack = 0;
2113 while (offJmpBack < 6)
2114 {
2115 cbInstr = 1;
2116 int rc = DISInstr(pbLdrLoadDll + offJmpBack, DISCPUMODE_64BIT, &Dis, &cbInstr);
2117 if ( RT_FAILURE(rc)
2118 || (Dis.pCurInstr->fOpType & (DISOPTYPE_CONTROLFLOW))
2119 || (Dis.ModRM.Bits.Mod == 0 && Dis.ModRM.Bits.Rm == 5 /* wrt RIP */) )
2120 supR3HardenedWinHookFailed("LdrLoadDll", pbLdrLoadDll);
2121 offJmpBack += cbInstr;
2122 }
2123# endif
2124
2125 /* Assemble the code for resuming the call.*/
2126 *(PFNRT *)&g_pfnLdrLoadDllReal = (PFNRT)(uintptr_t)&g_abSupHardReadWriteExecPage[offExecPage];
2127
2128 memcpy(&g_abSupHardReadWriteExecPage[offExecPage], pbLdrLoadDll, offJmpBack);
2129 offExecPage += offJmpBack;
2130
2131 g_abSupHardReadWriteExecPage[offExecPage++] = 0xff; /* jmp qword [$+8 wrt RIP] */
2132 g_abSupHardReadWriteExecPage[offExecPage++] = 0x25;
2133 *(uint32_t *)&g_abSupHardReadWriteExecPage[offExecPage] = RT_ALIGN_32(offExecPage + 4, 8) - (offExecPage + 4);
2134 offExecPage = RT_ALIGN_32(offExecPage + 4, 8);
2135 *(uint64_t *)&g_abSupHardReadWriteExecPage[offExecPage] = (uintptr_t)&pbLdrLoadDll[offJmpBack];
2136 offExecPage = RT_ALIGN_32(offJmpBack + 8, 16);
2137
2138 /* Patch the function. */
2139 *puJmpTab = (uintptr_t)supR3HardenedMonitor_LdrLoadDll;
2140
2141 SUPR3HARDENED_ASSERT_WIN32_SUCCESS(VirtualProtectEx(NtCurrentProcess(), pbLdrLoadDll, 16, PAGE_EXECUTE_READWRITE, &dwOldProt));
2142
2143 Assert(offJmpBack >= 6);
2144 pbLdrLoadDll[0] = 0xff;
2145 pbLdrLoadDll[1] = 0x25;
2146 *(uint32_t *)&pbLdrLoadDll[2] = (uint32_t)((uintptr_t)puJmpTab - (uintptr_t)&pbLdrLoadDll[2+4]);
2147
2148 SUPR3HARDENED_ASSERT_WIN32_SUCCESS(VirtualProtectEx(NtCurrentProcess(), pbLdrLoadDll, 16, PAGE_EXECUTE_READ, &dwOldProt));
2149 puJmpTab++;
2150
2151#else
2152 /*
2153 * Patch 32-bit hosts.
2154 */
2155# if 0
2156 /* Pattern #1:
2157 Windows 7:
2158 0:000> u ntdll!LdrLoadDll
2159 ntdll!LdrLoadDll:
2160 77aff585 8bff mov edi,edi
2161 77aff587 55 push ebp
2162 77aff588 8bec mov ebp,esp
2163 77aff58a 51 push ecx
2164 77aff58b 51 push ecx
2165 77aff58c a1f8bdaf77 mov eax,dword ptr [ntdll!LdrpLogLevelStateTable+0x24 (77afbdf8)]
2166
2167 Windows 8 rtm:
2168 0:000:x86> u ntdll_67150000!LdrLoadDll
2169 ntdll_67150000!LdrLoadDll:
2170 67189f3f 8bff mov edi,edi
2171 67189f41 55 push ebp
2172 67189f42 8bec mov ebp,esp
2173 67189f44 8b0d10eb2467 mov ecx,dword ptr [ntdll_67150000!LdrpDebugFlags (6724eb10)]
2174
2175 Windows 8.1:
2176 0:000:x86> u ntdll_w81_32!LdrLoadDll
2177 ntdll_w81_32!LdrLoadDll:
2178 6718aade 8bff mov edi,edi
2179 6718aae0 55 push ebp
2180 6718aae1 8bec mov ebp,esp
2181 6718aae3 83ec14 sub esp,14h
2182 6718aae6 f6050040246709 test byte ptr [ntdll_w81_32!LdrpDebugFlags (67244000)],9
2183
2184 Pattern #2:
2185 Windows XP:
2186 0:000:x86> u ntdll_xp!LdrLoadDll
2187 ntdll_xp!LdrLoadDll:
2188 77f569d2 6858020000 push 258h
2189 77f569d7 68d866f777 push offset ntdll_xp!`string'+0x12c (77f766d8)
2190 77f569dc e83bb20200 call ntdll_xp!_SEH_prolog (77f81c1c)
2191 77f569e1 33db xor ebx,ebx
2192 77f569e3 66895de0 mov word ptr [ebp-20h],bx
2193 77f569e7 33c0 xor eax,eax
2194 77f569e9 8d7de2 lea edi,[ebp-1Eh]
2195 77f569ec ab stos dword ptr es:[edi]
2196
2197 Windows Server 2003:
2198 0:000:x86> u ntdll_w2k3_32!LdrLoadDll
2199 ntdll_w2k3_32!LdrLoadDll:
2200 7c833f63 6840020000 push 240h
2201 7c833f68 68b040837c push offset ntdll_w2k3_32!`string'+0x12c (7c8340b0)
2202 7c833f6d e8a942ffff call ntdll_w2k3_32!_SEH_prolog (7c82821b)
2203 7c833f72 a13077887c mov eax,dword ptr [ntdll_w2k3_32!__security_cookie (7c887730)]
2204 7c833f77 8945e4 mov dword ptr [ebp-1Ch],eax
2205 7c833f7a 8b4508 mov eax,dword ptr [ebp+8]
2206 7c833f7d 8985b0fdffff mov dword ptr [ebp-250h],eax
2207 7c833f83 8b450c mov eax,dword ptr [ebp+0Ch]
2208
2209 Windows Vista SP0 & SP1:
2210 0:000:x86> u ntdll_vista_sp0_32!LdrLoadDll
2211 ntdll_vista_sp0_32!LdrLoadDll:
2212 69b0eb00 6844020000 push 244h
2213 69b0eb05 6838e9b269 push offset ntdll_vista_sp0_32! ?? ::FNODOBFM::`string'+0x39e (69b2e938)
2214 69b0eb0a e835420300 call ntdll_vista_sp0_32!_SEH_prolog4_GS (69b42d44)
2215 69b0eb0f 8b4508 mov eax,dword ptr [ebp+8]
2216 69b0eb12 8985acfdffff mov dword ptr [ebp-254h],eax
2217 69b0eb18 8b450c mov eax,dword ptr [ebp+0Ch]
2218 69b0eb1b 8985c0fdffff mov dword ptr [ebp-240h],eax
2219 69b0eb21 8b4510 mov eax,dword ptr [ebp+10h]
2220 */
2221
2222 if ( pbLdrLoadDll[0] == 0x8b /* mov edi, edi - for hot patching */
2223 && pbLdrLoadDll[1] == 0xff
2224 && pbLdrLoadDll[2] == 0x55 /* push ebp */
2225 && pbLdrLoadDll[3] == 0x8b /* mov ebp,esp */
2226 && pbLdrLoadDll[4] == 0xec)
2227 {
2228 offJmpBack = 5; /* the 3rd instruction. */
2229 pfnCallReal = supR3HardenedJmpBack_LdrLoadDll_Type1;
2230 }
2231 else if (pbLdrLoadDll[0] == 0x68 /* push dword XXXXXXXX */)
2232 {
2233 offJmpBack = 5;
2234 pfnCallReal = supR3HardenedJmpBack_LdrLoadDll_Type2;
2235 g_supR3HardenedJmpBack_LdrLoadDll_Type2_PushDword = *(uint32_t const *)&pbLdrLoadDll[1];
2236 }
2237 else
2238 supR3HardenedWinHookFailed("LdrLoadDll", pbLdrLoadDll);
2239
2240 g_pfnLdrLoadDllJmpBack = (PFNRT)(uintptr_t)(pbLdrLoadDll + offJmpBack);
2241 *(PFNRT *)&g_pfnLdrLoadDllReal = pfnCallReal;
2242
2243# else
2244 /* Just use the disassembler to skip 6 bytes or more. */
2245 DISSTATE Dis;
2246 uint32_t cbInstr;
2247 offJmpBack = 0;
2248 while (offJmpBack < 5)
2249 {
2250 cbInstr = 1;
2251 int rc = DISInstr(pbLdrLoadDll + offJmpBack, DISCPUMODE_32BIT, &Dis, &cbInstr);
2252 if ( RT_FAILURE(rc)
2253 || (Dis.pCurInstr->fOpType & (DISOPTYPE_CONTROLFLOW)) )
2254 supR3HardenedWinHookFailed("LdrLoadDll", pbLdrLoadDll);
2255 offJmpBack += cbInstr;
2256 }
2257
2258 /* Assemble the code for resuming the call.*/
2259 *(PFNRT *)&g_pfnLdrLoadDllReal = (PFNRT)(uintptr_t)&g_abSupHardReadWriteExecPage[offExecPage];
2260
2261 memcpy(&g_abSupHardReadWriteExecPage[offExecPage], pbLdrLoadDll, offJmpBack);
2262 offExecPage += offJmpBack;
2263
2264 g_abSupHardReadWriteExecPage[offExecPage++] = 0xe9;
2265 *(uint32_t *)&g_abSupHardReadWriteExecPage[offExecPage] = (uintptr_t)&pbLdrLoadDll[offJmpBack]
2266 - (uintptr_t)&g_abSupHardReadWriteExecPage[offExecPage + 4];
2267 offExecPage = RT_ALIGN_32(offJmpBack + 4, 16);
2268
2269# endif
2270
2271 /* Patch LdrLoadDLl. */
2272 SUPR3HARDENED_ASSERT_WIN32_SUCCESS(VirtualProtectEx(NtCurrentProcess(), pbLdrLoadDll, 16,
2273 PAGE_EXECUTE_READWRITE, &dwOldProt));
2274 Assert(offJmpBack >= 5);
2275 pbLdrLoadDll[0] = 0xe9;
2276 *(uint32_t *)&pbLdrLoadDll[1] = (uintptr_t)supR3HardenedMonitor_LdrLoadDll - (uintptr_t)&pbLdrLoadDll[1+4];
2277
2278 SUPR3HARDENED_ASSERT_WIN32_SUCCESS(VirtualProtectEx(NtCurrentProcess(), pbLdrLoadDll, 16, PAGE_EXECUTE_READ, &dwOldProt));
2279#endif
2280
2281 /*
2282 * Seal the rwx page.
2283 */
2284 SUPR3HARDENED_ASSERT_WIN32_SUCCESS(VirtualProtectEx(NtCurrentProcess(), g_abSupHardReadWriteExecPage, PAGE_SIZE,
2285 PAGE_EXECUTE_READ, &dwOldProt));
2286}
2287
2288
2289/**
2290 * Verifies the process integrity.
2291 */
2292DECLHIDDEN(void) supR3HardenedWinVerifyProcess(void)
2293{
2294 RTErrInfoInitStatic(&g_ErrInfoStatic);
2295 int rc = supHardenedWinVerifyProcess(NtCurrentProcess(), NtCurrentThread(),
2296 SUPHARDNTVPKIND_VERIFY_ONLY, &g_ErrInfoStatic.Core);
2297 if (RT_FAILURE(rc))
2298 supR3HardenedFatalMsg("supR3HardenedWinVerifyProcess", kSupInitOp_Integrity, rc,
2299 "Failed to verify process integrity: %s", g_ErrInfoStatic.szMsg);
2300}
2301
2302
2303/**
2304 * Gets the SID of the user associated with the process.
2305 *
2306 * @returns @c true if we've got a login SID, @c false if not.
2307 * @param pSidUser Where to return the user SID.
2308 * @param cbSidUser The size of the user SID buffer.
2309 * @param pSidLogin Where to return the login SID.
2310 * @param cbSidLogin The size of the login SID buffer.
2311 */
2312static bool supR3HardenedGetUserAndLogSids(PSID pSidUser, ULONG cbSidUser, PSID pSidLogin, ULONG cbSidLogin)
2313{
2314 HANDLE hToken;
2315 SUPR3HARDENED_ASSERT_NT_SUCCESS(NtOpenProcessToken(NtCurrentProcess(), TOKEN_QUERY, &hToken));
2316 union
2317 {
2318 TOKEN_USER UserInfo;
2319 TOKEN_GROUPS Groups;
2320 uint8_t abPadding[4096];
2321 } uBuf;
2322 ULONG cbRet = 0;
2323 SUPR3HARDENED_ASSERT_NT_SUCCESS(NtQueryInformationToken(hToken, TokenUser, &uBuf, sizeof(uBuf), &cbRet));
2324 SUPR3HARDENED_ASSERT_NT_SUCCESS(RtlCopySid(cbSidUser, pSidUser, uBuf.UserInfo.User.Sid));
2325
2326 bool fLoginSid = false;
2327 NTSTATUS rcNt = NtQueryInformationToken(hToken, TokenLogonSid, &uBuf, sizeof(uBuf), &cbRet);
2328 if (NT_SUCCESS(rcNt))
2329 {
2330 for (DWORD i = 0; i < uBuf.Groups.GroupCount; i++)
2331 if ((uBuf.Groups.Groups[i].Attributes & SE_GROUP_LOGON_ID) == SE_GROUP_LOGON_ID)
2332 {
2333 SUPR3HARDENED_ASSERT_NT_SUCCESS(RtlCopySid(cbSidLogin, pSidLogin, uBuf.Groups.Groups[i].Sid));
2334 fLoginSid = true;
2335 break;
2336 }
2337 }
2338
2339 SUPR3HARDENED_ASSERT_NT_SUCCESS(NtClose(hToken));
2340
2341 return fLoginSid;
2342}
2343
2344
2345/**
2346 * Build security attributes for the process or the primary thread (@a fProcess)
2347 *
2348 * Process DACLs can be bypassed using the SeDebugPrivilege (generally available
2349 * to admins, i.e. normal windows users), or by taking ownership and/or
2350 * modifying the DACL. However, it restricts
2351 *
2352 * @param pSecAttrs Where to return the security attributes.
2353 * @param pCleanup Cleanup record.
2354 * @param fProcess Set if it's for the process, clear if it's for
2355 * the primary thread.
2356 */
2357static void supR3HardenedInitSecAttrs(PSECURITY_ATTRIBUTES pSecAttrs, PMYSECURITYCLEANUP pCleanup, bool fProcess)
2358{
2359 /*
2360 * Safe return values.
2361 */
2362 suplibHardenedMemSet(pCleanup, 0, sizeof(*pCleanup));
2363
2364 pSecAttrs->nLength = sizeof(*pSecAttrs);
2365 pSecAttrs->bInheritHandle = FALSE;
2366 pSecAttrs->lpSecurityDescriptor = NULL;
2367
2368/** @todo This isn't at all complete, just sketches... */
2369
2370 /*
2371 * Create an ACL detailing the access of the above groups.
2372 */
2373 SUPR3HARDENED_ASSERT_NT_SUCCESS(RtlCreateAcl(&pCleanup->Acl.AclHdr, sizeof(pCleanup->Acl), ACL_REVISION));
2374
2375 ULONG fDeny = DELETE | WRITE_DAC | WRITE_OWNER | GENERIC_WRITE | GENERIC_EXECUTE | GENERIC_ALL;
2376 ULONG fAllow = SYNCHRONIZE | READ_CONTROL;
2377 ULONG fAllowLogin = SYNCHRONIZE | READ_CONTROL;
2378 if (fProcess)
2379 {
2380 fDeny |= PROCESS_CREATE_THREAD | PROCESS_SET_SESSIONID | PROCESS_VM_OPERATION | PROCESS_VM_WRITE
2381 | PROCESS_CREATE_PROCESS | PROCESS_DUP_HANDLE | PROCESS_SET_QUOTA
2382 | PROCESS_SET_INFORMATION | PROCESS_SUSPEND_RESUME;
2383 fAllow |= PROCESS_TERMINATE | PROCESS_VM_READ | PROCESS_QUERY_INFORMATION;
2384 fAllowLogin |= PROCESS_TERMINATE | PROCESS_VM_READ | PROCESS_QUERY_INFORMATION;
2385 if (g_uNtVerCombined >= SUP_MAKE_NT_VER_SIMPLE(6, 0)) /* Introduced in Vista. */
2386 {
2387 fAllow |= PROCESS_QUERY_LIMITED_INFORMATION;
2388 fAllowLogin |= PROCESS_QUERY_LIMITED_INFORMATION;
2389 }
2390 if (g_uNtVerCombined >= SUP_MAKE_NT_VER_SIMPLE(6, 3)) /* Introduced in Windows 8.1. */
2391 fAllow |= PROCESS_SET_LIMITED_INFORMATION;
2392 }
2393 else
2394 {
2395 fDeny |= THREAD_SUSPEND_RESUME | THREAD_SET_CONTEXT | THREAD_SET_INFORMATION | THREAD_SET_THREAD_TOKEN
2396 | THREAD_IMPERSONATE | THREAD_DIRECT_IMPERSONATION;
2397 fAllow |= THREAD_GET_CONTEXT | THREAD_QUERY_INFORMATION;
2398 fAllowLogin |= THREAD_GET_CONTEXT | THREAD_QUERY_INFORMATION;
2399 if (g_uNtVerCombined >= SUP_MAKE_NT_VER_SIMPLE(6, 0)) /* Introduced in Vista. */
2400 {
2401 fAllow |= THREAD_QUERY_LIMITED_INFORMATION | THREAD_SET_LIMITED_INFORMATION;
2402 fAllowLogin |= THREAD_QUERY_LIMITED_INFORMATION;
2403 }
2404
2405 }
2406 fDeny |= ~fAllow & (SPECIFIC_RIGHTS_ALL | STANDARD_RIGHTS_ALL);
2407
2408 /* Deny everyone access to bad bits. */
2409#if 1
2410 SID_IDENTIFIER_AUTHORITY SIDAuthWorld = SECURITY_WORLD_SID_AUTHORITY;
2411 SUPR3HARDENED_ASSERT_NT_SUCCESS(RtlInitializeSid(&pCleanup->Everyone.Sid, &SIDAuthWorld, 1));
2412 *RtlSubAuthoritySid(&pCleanup->Everyone.Sid, 0) = SECURITY_WORLD_RID;
2413 SUPR3HARDENED_ASSERT_NT_SUCCESS(RtlAddAccessDeniedAce(&pCleanup->Acl.AclHdr, ACL_REVISION,
2414 fDeny, &pCleanup->Everyone.Sid));
2415#endif
2416
2417#if 0
2418 /* Grant some access to the owner - doesn't work. */
2419 SID_IDENTIFIER_AUTHORITY SIDAuthCreator = SECURITY_CREATOR_SID_AUTHORITY;
2420 SUPR3HARDENED_ASSERT_NT_SUCCESS(RtlInitializeSid(&pCleanup->Owner.Sid, &SIDAuthCreator, 1));
2421 *RtlSubAuthoritySid(&pCleanup->Owner.Sid, 0) = SECURITY_CREATOR_OWNER_RID;
2422
2423 SUPR3HARDENED_ASSERT_NT_SUCCESS(RtlAddAccessDeniedAce(&pCleanup->Acl.AclHdr, ACL_REVISION,
2424 fDeny, &pCleanup->Owner.Sid));
2425 SUPR3HARDENED_ASSERT_NT_SUCCESS(RtlAddAccessAllowedAce(&pCleanup->Acl.AclHdr, ACL_REVISION,
2426 fAllow, &pCleanup->Owner.Sid));
2427#endif
2428
2429#if 1
2430 bool fHasLoginSid = supR3HardenedGetUserAndLogSids(&pCleanup->User.Sid, sizeof(pCleanup->User),
2431 &pCleanup->Login.Sid, sizeof(pCleanup->Login));
2432
2433# if 1
2434 /* Grant minimal access to the user. */
2435 SUPR3HARDENED_ASSERT_NT_SUCCESS(RtlAddAccessDeniedAce(&pCleanup->Acl.AclHdr, ACL_REVISION,
2436 fDeny, &pCleanup->User.Sid));
2437 SUPR3HARDENED_ASSERT_NT_SUCCESS(RtlAddAccessAllowedAce(&pCleanup->Acl.AclHdr, ACL_REVISION,
2438 fAllow, &pCleanup->User.Sid));
2439# endif
2440
2441# if 1
2442 /* Grant very limited access to the login sid. */
2443 if (fHasLoginSid)
2444 {
2445 SUPR3HARDENED_ASSERT_NT_SUCCESS(RtlAddAccessAllowedAce(&pCleanup->Acl.AclHdr, ACL_REVISION,
2446 fAllowLogin, &pCleanup->Login.Sid));
2447 }
2448# endif
2449
2450#endif
2451
2452 /*
2453 * Create a security descriptor with the above ACL.
2454 */
2455 PSECURITY_DESCRIPTOR pSecDesc = (PSECURITY_DESCRIPTOR)suplibHardenedAllocZ(SECURITY_DESCRIPTOR_MIN_LENGTH);
2456 pCleanup->pSecDesc = pSecDesc;
2457
2458 SUPR3HARDENED_ASSERT_NT_SUCCESS(RtlCreateSecurityDescriptor(pSecDesc, SECURITY_DESCRIPTOR_REVISION));
2459 SUPR3HARDENED_ASSERT_NT_SUCCESS(RtlSetDaclSecurityDescriptor(pSecDesc, TRUE /*fDaclPresent*/, &pCleanup->Acl.AclHdr,
2460 FALSE /*fDaclDefaulted*/));
2461 pSecAttrs->lpSecurityDescriptor = pSecDesc;
2462}
2463
2464
2465/**
2466 * Predicate function which tests whether @a ch is a argument separator
2467 * character.
2468 *
2469 * @returns True/false.
2470 * @param ch The character to examine.
2471 */
2472DECLINLINE(bool) suplibCommandLineIsArgSeparator(int ch)
2473{
2474 return ch == ' '
2475 || ch == '\t'
2476 || ch == '\n'
2477 || ch == '\r';
2478}
2479
2480
2481/**
2482 * Construct the new command line.
2483 *
2484 * Since argc/argv are both derived from GetCommandLineW (see
2485 * suplibHardenedWindowsMain), we skip the argument by argument UTF-8 -> UTF-16
2486 * conversion and quoting by going to the original source.
2487 *
2488 * The executable name, though, is replaced in case it's not a fullly
2489 * qualified path.
2490 *
2491 * The re-spawn indicator is added immediately after the executable name
2492 * so that we don't get tripped up missing close quote chars in the last
2493 * argument.
2494 *
2495 * @returns Pointer to a command line string (heap).
2496 * @param pUniStr Unicode string structure to initialize to the
2497 * command line. Optional.
2498 * @param iWhich Which respawn we're to check for, 1 being the first
2499 * one, and 2 the second and final.
2500 */
2501static PRTUTF16 supR3HardenedWinConstructCmdLine(PUNICODE_STRING pString, int iWhich)
2502{
2503 SUPR3HARDENED_ASSERT(iWhich == 1 || iWhich == 2);
2504
2505 /*
2506 * Get the command line and skip the executable name.
2507 */
2508 PUNICODE_STRING pCmdLineStr = &NtCurrentPeb()->ProcessParameters->CommandLine;
2509 PCRTUTF16 pawcArgs = pCmdLineStr->Buffer;
2510 uint32_t cwcArgs = pCmdLineStr->Length / sizeof(WCHAR);
2511
2512 /* Skip leading space (shouldn't be any, but whatever). */
2513 while (cwcArgs > 0 && suplibCommandLineIsArgSeparator(*pawcArgs) )
2514 cwcArgs--, pawcArgs++;
2515 SUPR3HARDENED_ASSERT(cwcArgs > 0 && *pawcArgs != '\0');
2516
2517 /* Walk to the end of it. */
2518 int fQuoted = false;
2519 do
2520 {
2521 if (*pawcArgs == '"')
2522 {
2523 fQuoted = !fQuoted;
2524 cwcArgs--; pawcArgs++;
2525 }
2526 else if (*pawcArgs != '\\' || (pawcArgs[1] != '\\' && pawcArgs[1] != '"'))
2527 cwcArgs--, pawcArgs++;
2528 else
2529 {
2530 unsigned cSlashes = 0;
2531 do
2532 {
2533 cSlashes++;
2534 cwcArgs--;
2535 pawcArgs++;
2536 }
2537 while (cwcArgs > 0 && *pawcArgs == '\\');
2538 if (cwcArgs > 0 && *pawcArgs == '"' && (cSlashes & 1))
2539 cwcArgs--, pawcArgs++; /* odd number of slashes == escaped quote */
2540 }
2541 } while (cwcArgs > 0 && (fQuoted || !suplibCommandLineIsArgSeparator(*pawcArgs)));
2542
2543 /* Skip trailing spaces. */
2544 while (cwcArgs > 0 && suplibCommandLineIsArgSeparator(*pawcArgs))
2545 cwcArgs--, pawcArgs++;
2546
2547 /*
2548 * Allocate a new buffer.
2549 */
2550 AssertCompile(sizeof(SUPR3_RESPAWN_1_ARG0) == sizeof(SUPR3_RESPAWN_2_ARG0));
2551 size_t cwcCmdLine = (sizeof(SUPR3_RESPAWN_1_ARG0) - 1) / sizeof(SUPR3_RESPAWN_1_ARG0[0]) /* Respawn exe name. */
2552 + !!cwcArgs + cwcArgs; /* if arguments present, add space + arguments. */
2553 if (cwcCmdLine * sizeof(WCHAR) >= 0xfff0)
2554 supR3HardenedFatalMsg("supR3HardenedWinConstructCmdLine", kSupInitOp_Misc, VERR_OUT_OF_RANGE,
2555 "Command line is too long (%u chars)!", cwcCmdLine);
2556
2557 PRTUTF16 pwszCmdLine = (PRTUTF16)HeapAlloc(GetProcessHeap(), 0 /* dwFlags*/, (cwcCmdLine + 1) * sizeof(RTUTF16));
2558 SUPR3HARDENED_ASSERT(pwszCmdLine != NULL);
2559
2560 /*
2561 * Construct the new command line.
2562 */
2563 PRTUTF16 pwszDst = pwszCmdLine;
2564 for (const char *pszSrc = iWhich == 1 ? SUPR3_RESPAWN_1_ARG0 : SUPR3_RESPAWN_2_ARG0; *pszSrc; pszSrc++)
2565 *pwszDst++ = *pszSrc;
2566
2567 if (cwcArgs)
2568 {
2569 *pwszDst++ = ' ';
2570 suplibHardenedMemCopy(pwszDst, pawcArgs, cwcArgs * sizeof(RTUTF16));
2571 pwszDst += cwcArgs;
2572 }
2573
2574 *pwszDst = '\0';
2575 SUPR3HARDENED_ASSERT(pwszDst - pwszCmdLine == cwcCmdLine);
2576
2577 if (pString)
2578 {
2579 pString->Buffer = pwszCmdLine;
2580 pString->Length = (USHORT)(cwcCmdLine * sizeof(WCHAR));
2581 pString->MaximumLength = pString->Length + sizeof(WCHAR);
2582 }
2583 return pwszCmdLine;
2584}
2585
2586
2587/**
2588 * Check if the zero terminated NT unicode string is the path to the given
2589 * system32 DLL.
2590 *
2591 * @returns true if it is, false if not.
2592 * @param pUniStr The zero terminated NT unicode string path.
2593 * @param pszName The name of the system32 DLL.
2594 */
2595static bool supR3HardNtIsNamedSystem32Dll(PUNICODE_STRING pUniStr, const char *pszName)
2596{
2597 if (pUniStr->Length > g_System32NtPath.UniStr.Length)
2598 {
2599 if (memcmp(pUniStr->Buffer, g_System32NtPath.UniStr.Buffer, g_System32NtPath.UniStr.Length) == 0)
2600 {
2601 if (pUniStr->Buffer[g_System32NtPath.UniStr.Length / sizeof(WCHAR)] == '\\')
2602 {
2603 if (RTUtf16ICmpAscii(&pUniStr->Buffer[g_System32NtPath.UniStr.Length / sizeof(WCHAR) + 1], pszName) == 0)
2604 return true;
2605 }
2606 }
2607 }
2608
2609 return false;
2610}
2611
2612
2613
2614/*
2615 * Child-Process Purification - release it from dubious influences.
2616 *
2617 * AV software and other things injecting themselves into the embryonic
2618 * and budding process to intercept API calls and what not. Unfortunately
2619 * this is also the behavior of viruses, malware and other unfriendly
2620 * software, so we won't stand for it. AV software can scan our image
2621 * as they are loaded via kernel hooks, that's sufficient. No need for
2622 * matching half of NTDLL or messing with the import table of the
2623 * process executable.
2624 */
2625
2626typedef struct SUPR3HARDNTPUCH
2627{
2628 /** Process handle. */
2629 HANDLE hProcess;
2630 /** Primary thread handle. */
2631 HANDLE hThread;
2632 /** Error buffer. */
2633 PRTERRINFO pErrInfo;
2634 /** The address of NTDLL in the child. */
2635 uintptr_t uNtDllAddr;
2636 /** The address of NTDLL in this process. */
2637 uintptr_t uNtDllParentAddr;
2638 /** The basic process info. */
2639 PROCESS_BASIC_INFORMATION BasicInfo;
2640 /** The probable size of the PEB. */
2641 size_t cbPeb;
2642 /** The pristine process environment block. */
2643 PEB Peb;
2644} SUPR3HARDNTPUCH;
2645typedef SUPR3HARDNTPUCH *PSUPR3HARDNTPUCH;
2646
2647
2648static int supR3HardNtPuChScrewUpPebForInitialImageEvents(PSUPR3HARDNTPUCH pThis)
2649{
2650 /*
2651 * Not sure if any of the cracker software uses the PEB at this point, but
2652 * just in case they do make some of the PEB fields a little less useful.
2653 */
2654 PEB Peb = pThis->Peb;
2655
2656 /* Make ImageBaseAddress useless. */
2657 Peb.ImageBaseAddress = (PVOID)((uintptr_t)Peb.ImageBaseAddress ^ UINT32_C(0x5f139000));
2658#ifdef RT_ARCH_AMD64
2659 Peb.ImageBaseAddress = (PVOID)((uintptr_t)Peb.ImageBaseAddress | UINT64_C(0x0313000000000000));
2660#endif
2661
2662 /*
2663 * Write the PEB.
2664 */
2665 SIZE_T cbActualMem = pThis->cbPeb;
2666 NTSTATUS rcNt = NtWriteVirtualMemory(pThis->hProcess, pThis->BasicInfo.PebBaseAddress, &Peb, pThis->cbPeb, &cbActualMem);
2667 if (!NT_SUCCESS(rcNt))
2668 return RTErrInfoSetF(pThis->pErrInfo, VERR_GENERAL_FAILURE, "NtWriteVirtualMemory/Peb failed: %#x", rcNt);
2669 return VINF_SUCCESS;
2670}
2671
2672
2673/**
2674 * Trigger the initial image events without actually initializing the process.
2675 *
2676 * This is a trick to force sysplant.sys to call its hand by tripping the image
2677 * loaded event for the main executable and ntdll images. This will happen when
2678 * the first thread in a process starts executing in PspUserThreadStartup. We
2679 * create a second thread that quits immediately by means of temporarily
2680 * replacing ntdll!LdrInitializeThunk by a NtTerminateThread call.
2681 * (LdrInitializeThunk is called by way of an APC queued the thread is created,
2682 * thus NtSetContextThread is of no use.)
2683 *
2684 * @returns VBox status code.
2685 * @param pThis The child cleanup
2686 * @param pErrInfo For extended error information.
2687 */
2688static int supR3HardNtPuChTriggerInitialImageEvents(PSUPR3HARDNTPUCH pThis)
2689{
2690 /*
2691 * Use the on-disk image for the ntdll entrypoints here.
2692 */
2693 PSUPHNTLDRCACHEENTRY pLdrEntry;
2694 int rc = supHardNtLdrCacheOpen("ntdll.dll", &pLdrEntry);
2695 if (RT_FAILURE(rc))
2696 return RTErrInfoSetF(pThis->pErrInfo, rc, "supHardNtLdrCacheOpen failed on NTDLL: %Rrc", rc);
2697
2698 RTLDRADDR uLdrInitThunk;
2699 rc = RTLdrGetSymbolEx(pLdrEntry->hLdrMod, pLdrEntry->pbBits, pThis->uNtDllAddr, UINT32_MAX,
2700 "LdrInitializeThunk", &uLdrInitThunk);
2701 if (RT_FAILURE(rc))
2702 return RTErrInfoSetF(pThis->pErrInfo, rc, "Error locating LdrInitializeThunk in NTDLL: %Rrc", rc);
2703 PVOID pvLdrInitThunk = (PVOID)(uintptr_t)uLdrInitThunk;
2704
2705 RTLDRADDR uNtTerminateThread;
2706 rc = RTLdrGetSymbolEx(pLdrEntry->hLdrMod, pLdrEntry->pbBits, pThis->uNtDllAddr, UINT32_MAX,
2707 "NtTerminateThread", &uNtTerminateThread);
2708 if (RT_FAILURE(rc))
2709 return RTErrInfoSetF(pThis->pErrInfo, rc, "Error locating NtTerminateThread in NTDLL: %Rrc", rc);
2710
2711 SUP_DPRINTF(("supR3HardNtPuChTriggerInitialImageEvents: uLdrInitThunk=%p uNtTerminateThread=%p\n",
2712 (uintptr_t)uLdrInitThunk, (uintptr_t)uNtTerminateThread));
2713
2714 /*
2715 * Back up the thunk code.
2716 */
2717 uint8_t abBackup[16];
2718 SIZE_T cbIgnored;
2719 NTSTATUS rcNt = NtReadVirtualMemory(pThis->hProcess, pvLdrInitThunk, abBackup, sizeof(abBackup), &cbIgnored);
2720 if (!NT_SUCCESS(rcNt))
2721 return RTErrInfoSetF(pThis->pErrInfo, VERR_GENERAL_FAILURE,
2722 "NtReadVirtualMemory/LdrInitializeThunk failed: %#x", rcNt);
2723
2724 /*
2725 * Cook up replacement code that calls NtTerminateThread.
2726 */
2727 uint8_t abReplacement[sizeof(abBackup)] ;
2728 memcpy(abReplacement, abBackup, sizeof(abReplacement));
2729
2730#ifdef RT_ARCH_AMD64
2731 abReplacement[0] = 0x31; /* xor ecx, ecx */
2732 abReplacement[1] = 0xc9;
2733 abReplacement[2] = 0x31; /* xor edx, edx */
2734 abReplacement[3] = 0xd2;
2735 abReplacement[4] = 0xe8; /* call near NtTerminateThread */
2736 *(int32_t *)&abReplacement[5] = (int32_t)(uNtTerminateThread - (uLdrInitThunk + 9));
2737 abReplacement[9] = 0xcc; /* int3 */
2738#elif defined(RT_ARCH_X86)
2739 abReplacement[0] = 0x6a; /* push 0 */
2740 abReplacement[1] = 0x00;
2741 abReplacement[2] = 0x6a; /* push 0 */
2742 abReplacement[3] = 0x00;
2743 abReplacement[4] = 0xe8; /* call near NtTerminateThread */
2744 *(int32_t *)&abReplacement[5] = (int32_t)(uNtTerminateThread - (uLdrInitThunk + 9));
2745 abReplacement[9] = 0xcc; /* int3 */
2746#else
2747# error "Unsupported arch."
2748#endif
2749
2750 /*
2751 * Install the replacment code.
2752 */
2753 PVOID pvProt = pvLdrInitThunk;
2754 SIZE_T cbProt = 16;
2755 ULONG fOldProt = 0;
2756 rcNt = NtProtectVirtualMemory(pThis->hProcess, &pvProt, &cbProt, PAGE_EXECUTE_READWRITE, &fOldProt);
2757 if (!NT_SUCCESS(rcNt))
2758 return RTErrInfoSetF(pThis->pErrInfo, VERR_GENERAL_FAILURE,
2759 "NtProtectVirtualMemory/LdrInitializeThunk failed: %#x", rcNt);
2760
2761 rcNt = NtWriteVirtualMemory(pThis->hProcess, pvLdrInitThunk, abReplacement, sizeof(abReplacement), &cbIgnored);
2762 if (!NT_SUCCESS(rcNt))
2763 return RTErrInfoSetF(pThis->pErrInfo, VERR_GENERAL_FAILURE,
2764 "NtWriteVirtualMemory/LdrInitializeThunk failed: %#x", rcNt);
2765
2766 /*
2767 * Create the thread, waiting 10 seconds for it to complete.
2768 */
2769 CLIENT_ID Thread2Id;
2770 HANDLE hThread2;
2771 rcNt = RtlCreateUserThread(pThis->hProcess,
2772 NULL /* SecurityAttribs */,
2773 FALSE /* CreateSuspended */,
2774 0 /* ZeroBits */,
2775 0 /* MaximumStackSize */,
2776 0 /* CommittedStackSize */,
2777 (PFNRT)2 /* StartAddress */,
2778 NULL /*Parameter*/ ,
2779 &hThread2,
2780 &Thread2Id);
2781 if (NT_SUCCESS(rcNt))
2782 {
2783 LARGE_INTEGER Timeout;
2784 Timeout.QuadPart = -10 * 10000000; /* 10 seconds */
2785 NtWaitForSingleObject(hThread2, FALSE /* Alertable */, &Timeout);
2786 NtTerminateThread(hThread2, DBG_TERMINATE_THREAD);
2787 NtClose(hThread2);
2788 }
2789
2790 /*
2791 * Restore the original thunk code and protection.
2792 */
2793 rcNt = NtWriteVirtualMemory(pThis->hProcess, pvLdrInitThunk, abBackup, sizeof(abBackup), &cbIgnored);
2794 if (!NT_SUCCESS(rcNt))
2795 return RTErrInfoSetF(pThis->pErrInfo, VERR_GENERAL_FAILURE,
2796 "NtWriteVirtualMemory/LdrInitializeThunk[restore] failed: %#x", rcNt);
2797
2798 pvProt = pvLdrInitThunk;
2799 cbProt = 16;
2800 rcNt = NtProtectVirtualMemory(pThis->hProcess, &pvProt, &cbProt, fOldProt, &fOldProt);
2801 if (!NT_SUCCESS(rcNt))
2802 return RTErrInfoSetF(pThis->pErrInfo, VERR_GENERAL_FAILURE,
2803 "NtProtectVirtualMemory/LdrInitializeThunk[restore] failed: %#x", rcNt);
2804
2805 return VINF_SUCCESS;
2806}
2807
2808#if 0
2809static int supR3HardenedWinScratchChildMemory(HANDLE hProcess, void *pv, size_t cb, const char *pszWhat, PRTERRINFO pErrInfo)
2810{
2811 SUP_DPRINTF(("supR3HardenedWinScratchChildMemory: %p %#x\n", pv, cb));
2812
2813 PVOID pvCopy = pv;
2814 SIZE_T cbCopy = cb;
2815 NTSTATUS rcNt = NtProtectVirtualMemory(hProcess, &pvCopy, &cbCopy, PAGE_NOACCESS, NULL);
2816 if (!NT_SUCCESS(rcNt))
2817 return RTErrInfoSetF(pErrInfo, VERR_GENERAL_FAILURE, "NtProtectVirtualMemory/%s (%p LB %#zx) failed: %#x",
2818 pszWhat, pv, cb, rcNt);
2819 return VINF_SUCCESS;
2820}
2821#endif
2822
2823
2824static int supR3HardNtPuChSanitizePeb(PSUPR3HARDNTPUCH pThis)
2825{
2826 /*
2827 * Make a copy of the pre-execution PEB.
2828 */
2829 PEB Peb = pThis->Peb;
2830
2831#if 0
2832 /*
2833 * There should not be any activation context, so if there is, we scratch the memory associated with it.
2834 */
2835 int rc = 0;
2836 if (RT_SUCCESS(rc) && Peb.pShimData && !((uintptr_t)Peb.pShimData & PAGE_OFFSET_MASK))
2837 rc = supR3HardenedWinScratchChildMemory(hProcess, Peb.pShimData, PAGE_SIZE, "pShimData", pErrInfo);
2838 if (RT_SUCCESS(rc) && Peb.ActivationContextData && !((uintptr_t)Peb.ActivationContextData & PAGE_OFFSET_MASK))
2839 rc = supR3HardenedWinScratchChildMemory(hProcess, Peb.ActivationContextData, PAGE_SIZE, "ActivationContextData", pErrInfo);
2840 if (RT_SUCCESS(rc) && Peb.ProcessAssemblyStorageMap && !((uintptr_t)Peb.ProcessAssemblyStorageMap & PAGE_OFFSET_MASK))
2841 rc = supR3HardenedWinScratchChildMemory(hProcess, Peb.ProcessAssemblyStorageMap, PAGE_SIZE, "ProcessAssemblyStorageMap", pErrInfo);
2842 if (RT_SUCCESS(rc) && Peb.SystemDefaultActivationContextData && !((uintptr_t)Peb.SystemDefaultActivationContextData & PAGE_OFFSET_MASK))
2843 rc = supR3HardenedWinScratchChildMemory(hProcess, Peb.ProcessAssemblyStorageMap, PAGE_SIZE, "SystemDefaultActivationContextData", pErrInfo);
2844 if (RT_SUCCESS(rc) && Peb.SystemAssemblyStorageMap && !((uintptr_t)Peb.SystemAssemblyStorageMap & PAGE_OFFSET_MASK))
2845 rc = supR3HardenedWinScratchChildMemory(hProcess, Peb.SystemAssemblyStorageMap, PAGE_SIZE, "SystemAssemblyStorageMap", pErrInfo);
2846 if (RT_FAILURE(rc))
2847 return rc;
2848#endif
2849
2850 /*
2851 * Clear compatibility and activation related fields.
2852 */
2853 Peb.AppCompatFlags.QuadPart = 0;
2854 Peb.AppCompatFlagsUser.QuadPart = 0;
2855 Peb.pShimData = NULL;
2856 Peb.AppCompatInfo = NULL;
2857#if 0
2858 Peb.ActivationContextData = NULL;
2859 Peb.ProcessAssemblyStorageMap = NULL;
2860 Peb.SystemDefaultActivationContextData = NULL;
2861 Peb.SystemAssemblyStorageMap = NULL;
2862 /*Peb.Diff0.W6.IsProtectedProcess = 1;*/
2863#endif
2864
2865 /*
2866 * Write back the PEB.
2867 */
2868 SIZE_T cbActualMem = pThis->cbPeb;
2869 NTSTATUS rcNt = NtWriteVirtualMemory(pThis->hProcess, pThis->BasicInfo.PebBaseAddress, &Peb, pThis->cbPeb, &cbActualMem);
2870 if (!NT_SUCCESS(rcNt))
2871 return RTErrInfoSetF(pThis->pErrInfo, VERR_GENERAL_FAILURE, "NtWriteVirtualMemory/Peb failed: %#x", rcNt);
2872
2873 return VINF_SUCCESS;
2874}
2875
2876
2877static void supR3HardNtPuChFindNtdll(PSUPR3HARDNTPUCH pThis)
2878{
2879 /*
2880 * Find NTDLL in this process first and take that as a starting point.
2881 */
2882 pThis->uNtDllParentAddr = (uintptr_t)GetModuleHandleW(L"ntdll.dll");
2883 SUPR3HARDENED_ASSERT(pThis->uNtDllParentAddr != 0 && !(pThis->uNtDllParentAddr & PAGE_OFFSET_MASK));
2884 pThis->uNtDllAddr = pThis->uNtDllParentAddr;
2885
2886 /*
2887 * Scan the virtual memory of the child.
2888 */
2889 uintptr_t cbAdvance = 0;
2890 uintptr_t uPtrWhere = 0;
2891 for (uint32_t i = 0; i < 1024; i++)
2892 {
2893 /* Query information. */
2894 SIZE_T cbActual = 0;
2895 MEMORY_BASIC_INFORMATION MemInfo = { 0, 0, 0, 0, 0, 0, 0 };
2896 NTSTATUS rcNt = NtQueryVirtualMemory(pThis->hProcess,
2897 (void const *)uPtrWhere,
2898 MemoryBasicInformation,
2899 &MemInfo,
2900 sizeof(MemInfo),
2901 &cbActual);
2902 if (!NT_SUCCESS(rcNt))
2903 break;
2904
2905 if ( MemInfo.Type == SEC_IMAGE
2906 || MemInfo.Type == SEC_PROTECTED_IMAGE
2907 || MemInfo.Type == (SEC_IMAGE | SEC_PROTECTED_IMAGE))
2908 {
2909 if (MemInfo.BaseAddress == MemInfo.AllocationBase)
2910 {
2911 /* Get the image name. */
2912 union
2913 {
2914 UNICODE_STRING UniStr;
2915 uint8_t abPadding[4096];
2916 } uBuf;
2917 NTSTATUS rcNt = NtQueryVirtualMemory(pThis->hProcess,
2918 MemInfo.BaseAddress,
2919 MemorySectionName,
2920 &uBuf,
2921 sizeof(uBuf) - sizeof(WCHAR),
2922 &cbActual);
2923 if (NT_SUCCESS(rcNt))
2924 {
2925 uBuf.UniStr.Buffer[uBuf.UniStr.Length / sizeof(WCHAR)] = '\0';
2926 if (supR3HardNtIsNamedSystem32Dll(&uBuf.UniStr, "ntdll.dll"))
2927 {
2928 pThis->uNtDllAddr = (uintptr_t)MemInfo.AllocationBase;
2929 SUP_DPRINTF(("supR3HardNtPuChFindNtdll: uNtDllParentAddr=%p uNtDllChildAddr=%p\n",
2930 pThis->uNtDllParentAddr, pThis->uNtDllAddr));
2931 return;
2932 }
2933 }
2934 }
2935 }
2936
2937 /*
2938 * Advance.
2939 */
2940 cbAdvance = MemInfo.RegionSize;
2941 if (uPtrWhere + cbAdvance <= uPtrWhere)
2942 break;
2943 uPtrWhere += MemInfo.RegionSize;
2944 }
2945
2946#ifdef DEBUG
2947 supR3HardenedFatal("%s: ntdll.dll not found in child.", __FUNCTION__);
2948#endif
2949}
2950
2951
2952
2953static int supR3HardenedWinPurifyChild(HANDLE hProcess, HANDLE hThread, PRTERRINFO pErrInfo)
2954{
2955 /*
2956 * Initialize the purifier instance data.
2957 */
2958 SUPR3HARDNTPUCH This;
2959 This.hProcess = hProcess;
2960 This.hThread = hThread;
2961 This.pErrInfo = pErrInfo;
2962
2963 ULONG cbActual = 0;
2964 NTSTATUS rcNt = NtQueryInformationProcess(hProcess, ProcessBasicInformation,
2965 &This.BasicInfo, sizeof(This.BasicInfo), &cbActual);
2966 if (!NT_SUCCESS(rcNt))
2967 return RTErrInfoSetF(pErrInfo, VERR_GENERAL_FAILURE,
2968 "NtQueryInformationProcess/ProcessBasicInformation failed: %#x", rcNt);
2969
2970 if (g_uNtVerCombined < SUP_NT_VER_W2K3)
2971 This.cbPeb = PEB_SIZE_W51;
2972 else if (g_uNtVerCombined < SUP_NT_VER_VISTA)
2973 This.cbPeb = PEB_SIZE_W52;
2974 else if (g_uNtVerCombined < SUP_NT_VER_W70)
2975 This.cbPeb = PEB_SIZE_W6;
2976 else if (g_uNtVerCombined < SUP_NT_VER_W80)
2977 This.cbPeb = PEB_SIZE_W7;
2978 else if (g_uNtVerCombined < SUP_NT_VER_W81)
2979 This.cbPeb = PEB_SIZE_W80;
2980 else
2981 This.cbPeb = PEB_SIZE_W81;
2982
2983 SUP_DPRINTF(("supR3HardenedWinPurifyChild: PebBaseAddress=%p cbPeb=%#x\n", This.BasicInfo.PebBaseAddress, This.cbPeb));
2984
2985 SIZE_T cbActualMem;
2986 RT_ZERO(This.Peb);
2987 rcNt = NtReadVirtualMemory(hProcess, This.BasicInfo.PebBaseAddress, &This.Peb, sizeof(This.Peb), &cbActualMem);
2988 if (!NT_SUCCESS(rcNt))
2989 return RTErrInfoSetF(pErrInfo, VERR_GENERAL_FAILURE, "NtReadVirtualMemory/Peb failed: %#x", rcNt);
2990
2991 supR3HardNtPuChFindNtdll(&This);
2992
2993 /*
2994 * Do the work, the last bit we tag along with the process verfication code.
2995 */
2996 int rc = supR3HardNtPuChScrewUpPebForInitialImageEvents(&This);
2997 if (RT_SUCCESS(rc))
2998 rc = supR3HardNtPuChTriggerInitialImageEvents(&This);
2999 if (RT_SUCCESS(rc))
3000 rc = supR3HardNtPuChSanitizePeb(&This);
3001 if (RT_SUCCESS(rc))
3002 rc = supHardenedWinVerifyProcess(hProcess, hThread, SUPHARDNTVPKIND_CHILD_PURIFICATION, pErrInfo);
3003
3004 return rc;
3005}
3006
3007
3008/**
3009 * Does the actually respawning.
3010 *
3011 * @returns Never, will call exit or raise fatal error.
3012 * @param iWhich Which respawn we're to check for, 1 being the
3013 * first one, and 2 the second and final.
3014 *
3015 * @todo Split up this function.
3016 */
3017static int supR3HardenedWinDoReSpawn(int iWhich)
3018{
3019 NTSTATUS rcNt;
3020 PPEB pPeb = NtCurrentPeb();
3021 PRTL_USER_PROCESS_PARAMETERS pParentProcParams = pPeb->ProcessParameters;
3022
3023 SUPR3HARDENED_ASSERT(g_cSuplibHardenedWindowsMainCalls == 1);
3024
3025 /*
3026 * Set up security descriptors.
3027 */
3028 SECURITY_ATTRIBUTES ProcessSecAttrs;
3029 MYSECURITYCLEANUP ProcessSecAttrsCleanup;
3030 supR3HardenedInitSecAttrs(&ProcessSecAttrs, &ProcessSecAttrsCleanup, true /*fProcess*/);
3031
3032 SECURITY_ATTRIBUTES ThreadSecAttrs;
3033 MYSECURITYCLEANUP ThreadSecAttrsCleanup;
3034 supR3HardenedInitSecAttrs(&ThreadSecAttrs, &ThreadSecAttrsCleanup, false /*fProcess*/);
3035
3036#if 1
3037 /*
3038 * Configure the startup info and creation flags.
3039 */
3040 DWORD dwCreationFlags = CREATE_SUSPENDED;
3041
3042 STARTUPINFOEXW SiEx;
3043 suplibHardenedMemSet(&SiEx, 0, sizeof(SiEx));
3044 if (1)
3045 SiEx.StartupInfo.cb = sizeof(SiEx.StartupInfo);
3046 else
3047 {
3048 SiEx.StartupInfo.cb = sizeof(SiEx);
3049 dwCreationFlags |= EXTENDED_STARTUPINFO_PRESENT;
3050 /** @todo experiment with protected process stuff later on. */
3051 }
3052
3053 SiEx.StartupInfo.dwFlags |= STARTF_USESTDHANDLES;
3054 SiEx.StartupInfo.hStdInput = pParentProcParams->StandardInput;
3055 SiEx.StartupInfo.hStdOutput = pParentProcParams->StandardOutput;
3056 SiEx.StartupInfo.hStdError = pParentProcParams->StandardError;
3057
3058 /*
3059 * Construct the command line and launch the process.
3060 */
3061 PRTUTF16 pwszCmdLine = supR3HardenedWinConstructCmdLine(NULL, iWhich);
3062
3063 PROCESS_INFORMATION ProcessInfoW32;
3064 if (!CreateProcessW(g_wszSupLibHardenedExePath,
3065 pwszCmdLine,
3066 &ProcessSecAttrs,
3067 &ThreadSecAttrs,
3068 TRUE /*fInheritHandles*/,
3069 dwCreationFlags,
3070 NULL /*pwszzEnvironment*/,
3071 NULL /*pwszCurDir*/,
3072 &SiEx.StartupInfo,
3073 &ProcessInfoW32))
3074 supR3HardenedFatalMsg("supR3HardenedWinReSpawn", kSupInitOp_Misc, VERR_INVALID_NAME,
3075 "Error relaunching VirtualBox VM process: %u\n"
3076 "Command line: '%ls'",
3077 GetLastError(), pwszCmdLine);
3078
3079 SUP_DPRINTF(("supR3HardenedWinDoReSpawn(%d): New child %x.%x [kernel32].\n",
3080 iWhich, ProcessInfoW32.dwProcessId, ProcessInfoW32.dwThreadId));
3081 HANDLE hProcess = ProcessInfoW32.hProcess;
3082 HANDLE hThread = ProcessInfoW32.hThread;
3083
3084#else
3085
3086 /*
3087 * Construct the process parameters.
3088 */
3089 UNICODE_STRING W32ImageName;
3090 W32ImageName.Buffer = g_wszSupLibHardenedExePath; /* Yes the windows name for the process parameters. */
3091 W32ImageName.Length = (USHORT)RTUtf16Len(g_wszSupLibHardenedExePath) * sizeof(WCHAR);
3092 W32ImageName.MaximumLength = W32ImageName.Length + sizeof(WCHAR);
3093
3094 UNICODE_STRING CmdLine;
3095 supR3HardenedWinConstructCmdLine(&CmdLine, iWhich);
3096
3097 PRTL_USER_PROCESS_PARAMETERS pProcParams = NULL;
3098 SUPR3HARDENED_ASSERT_NT_SUCCESS(RtlCreateProcessParameters(&pProcParams,
3099 &W32ImageName,
3100 NULL /* DllPath - inherit from this process */,
3101 NULL /* CurrentDirectory - inherit from this process */,
3102 &CmdLine,
3103 NULL /* Environment - inherit from this process */,
3104 NULL /* WindowsTitle - none */,
3105 NULL /* DesktopTitle - none. */,
3106 NULL /* ShellInfo - none. */,
3107 NULL /* RuntimeInfo - none (byte array for MSVCRT file info) */)
3108 );
3109
3110 /** @todo this doesn't work. :-( */
3111 pProcParams->ConsoleHandle = pParentProcParams->ConsoleHandle;
3112 pProcParams->ConsoleFlags = pParentProcParams->ConsoleFlags;
3113 pProcParams->StandardInput = pParentProcParams->StandardInput;
3114 pProcParams->StandardOutput = pParentProcParams->StandardOutput;
3115 pProcParams->StandardError = pParentProcParams->StandardError;
3116
3117 RTL_USER_PROCESS_INFORMATION ProcessInfoNt = { sizeof(ProcessInfoNt) };
3118 rcNt = RtlCreateUserProcess(&g_SupLibHardenedExeNtPath.UniStr,
3119 OBJ_INHERIT | OBJ_CASE_INSENSITIVE /*Attributes*/,
3120 pProcParams,
3121 NULL, //&ProcessSecAttrs,
3122 NULL, //&ThreadSecAttrs,
3123 NtCurrentProcess() /* ParentProcess */,
3124 FALSE /*fInheritHandles*/,
3125 NULL /* DebugPort */,
3126 NULL /* ExceptionPort */,
3127 &ProcessInfoNt);
3128 if (!NT_SUCCESS(rcNt))
3129 supR3HardenedFatalMsg("supR3HardenedWinReSpawn", kSupInitOp_Misc, VERR_INVALID_NAME,
3130 "Error relaunching VirtualBox VM process: %#x\n"
3131 "Command line: '%ls'",
3132 rcNt, CmdLine.Buffer);
3133
3134 SUP_DPRINTF(("supR3HardenedWinDoReSpawn(%d): New child %x.%x [ntdll].\n",
3135 iWhich, ProcessInfo.ClientId.UniqueProcess, ProcessInfo.ClientId.UniqueThread));
3136 RtlDestroyProcessParameters(pProcParams);
3137
3138 HANDLE hProcess = ProcessInfoNt.ProcessHandle;
3139 HANDLE hThread = ProcessInfoNt.ThreadHandle;
3140#endif
3141
3142#ifndef VBOX_WITHOUT_DEBUGGER_CHECKS
3143 /*
3144 * Apply anti debugger notification trick to the thread. (Also done in
3145 * supR3HardenedWinInstallHooks.)
3146 */
3147 rcNt = NtSetInformationThread(NtCurrentThread(), ThreadHideFromDebugger, NULL, 0);
3148 if (!NT_SUCCESS(rcNt))
3149 {
3150 NtTerminateProcess(hProcess, DBG_TERMINATE_PROCESS);
3151 supR3HardenedError(rcNt, true /*fFatal*/, "NtSetInformationThread/ThreadHideFromDebugger failed: %#x\n", rcNt);
3152 }
3153#endif
3154
3155 /*
3156 * Clean up the process.
3157 */
3158 int rc = supR3HardenedWinPurifyChild(hProcess, hThread, RTErrInfoInitStatic(&g_ErrInfoStatic));
3159 if (RT_FAILURE(rc))
3160 {
3161 NtTerminateProcess(hProcess, DBG_TERMINATE_PROCESS);
3162 supR3HardenedError(rc, true /*fFatal*/, "%s", g_ErrInfoStatic.szMsg);
3163 }
3164
3165 /*
3166 * Start the process execution.
3167 */
3168 ULONG cSuspendCount = 0;
3169 SUPR3HARDENED_ASSERT_NT_SUCCESS(NtResumeThread(hThread, &cSuspendCount));
3170
3171 /*
3172 * Close the unrestricted access handles. Since we need to wait on the
3173 * child process, we'll reopen the process with limited access before doing
3174 * away with the process handle returned by CreateProcess.
3175 */
3176 SUPR3HARDENED_ASSERT_NT_SUCCESS(NtClose(hThread));
3177
3178 PROCESS_BASIC_INFORMATION BasicInfo;
3179 HANDLE hProcWait;
3180 ULONG fRights = SYNCHRONIZE;
3181 if (g_uNtVerCombined >= SUP_MAKE_NT_VER_SIMPLE(6, 0)) /* Introduced in Vista. */
3182 fRights |= PROCESS_QUERY_LIMITED_INFORMATION;
3183 else
3184 fRights |= PROCESS_QUERY_INFORMATION;
3185 rcNt = NtDuplicateObject(NtCurrentProcess(), hProcess,
3186 NtCurrentProcess(), &hProcWait,
3187 fRights, 0 /*HandleAttributes*/, 0);
3188 if (rcNt == STATUS_ACCESS_DENIED)
3189 rcNt = NtDuplicateObject(NtCurrentProcess(), hProcess,
3190 NtCurrentProcess(), &hProcWait,
3191 SYNCHRONIZE, 0 /*HandleAttributes*/, 0);
3192 if (!NT_SUCCESS(rcNt))
3193 {
3194 /* Failure is unacceptable, kill the process. */
3195 NtTerminateProcess(hProcess, RTEXITCODE_FAILURE);
3196 supR3HardenedError(rcNt, false /*fFatal*/, "NtDuplicateObject failed on child process handle: %#x\n", rcNt);
3197
3198 NTSTATUS rcNtExit = NtQueryInformationProcess(hProcess, ProcessBasicInformation, &BasicInfo, sizeof(BasicInfo), NULL);
3199 bool fExitOk = NT_SUCCESS(rcNtExit) && BasicInfo.ExitStatus != STATUS_PENDING;
3200 if (!fExitOk)
3201 {
3202 NTSTATUS rcNtWait;
3203 DWORD dwStartTick = GetTickCount();
3204 do
3205 {
3206 NtTerminateProcess(hProcess, DBG_TERMINATE_PROCESS);
3207
3208 LARGE_INTEGER Timeout;
3209 Timeout.QuadPart = -20000000; /* 2 second */
3210 rcNtWait = NtWaitForSingleObject(hProcess, TRUE /*Alertable*/, &Timeout);
3211
3212 rcNtExit = NtQueryInformationProcess(hProcess, ProcessBasicInformation, &BasicInfo, sizeof(BasicInfo), NULL);
3213 fExitOk = NT_SUCCESS(rcNtExit) && BasicInfo.ExitStatus != STATUS_PENDING;
3214 } while ( !fExitOk
3215 && ( rcNtWait == STATUS_TIMEOUT
3216 || rcNtWait == STATUS_USER_APC
3217 || rcNtWait == STATUS_ALERTED)
3218 && GetTickCount() - dwStartTick < 60 * 1000);
3219 if (fExitOk)
3220 supR3HardenedError(rcNt, false /*fFatal*/,
3221 "NtDuplicateObject failed and we failed to kill child: rcNt=%u rcNtWait=%u hProcess=%p\n",
3222 rcNt, rcNtWait, hProcess);
3223 }
3224 supR3HardenedFatalMsg("supR3HardenedWinReSpawn", kSupInitOp_Misc, VERR_INVALID_NAME,
3225 "NtDuplicateObject failed on child process handle: %#x\n", rcNt);
3226 }
3227
3228 SUPR3HARDENED_ASSERT_NT_SUCCESS(NtClose(hProcess));
3229 hProcess = NULL;
3230
3231 /*
3232 * Ditch the loader cache so we don't sit on too much memory while waiting.
3233 */
3234 supR3HardenedWinFlushLoaderCache();
3235 HeapCompact(GetProcessHeap(), 0 /*dwFlags*/);
3236
3237 /*
3238 * If this is the middle process, wait for both parent and child to quit.
3239 */
3240 HANDLE hParent = NULL;
3241 if (iWhich > 1)
3242 {
3243 rcNt = NtQueryInformationProcess(NtCurrentProcess(), ProcessBasicInformation, &BasicInfo, sizeof(BasicInfo), NULL);
3244 if (NT_SUCCESS(rcNt))
3245 {
3246 OBJECT_ATTRIBUTES ObjAttr;
3247 InitializeObjectAttributes(&ObjAttr, NULL, 0, NULL /*hRootDir*/, NULL /*pSecDesc*/);
3248
3249 CLIENT_ID ClientId;
3250 ClientId.UniqueProcess = (HANDLE)BasicInfo.InheritedFromUniqueProcessId;
3251 ClientId.UniqueThread = NULL;
3252
3253 rcNt = NtOpenProcess(&hParent, SYNCHRONIZE | PROCESS_QUERY_INFORMATION, &ObjAttr, &ClientId);
3254 }
3255#ifdef DEBUG
3256 SUPR3HARDENED_ASSERT_NT_SUCCESS(rcNt);
3257#endif
3258 }
3259
3260 if (hParent != NULL)
3261 {
3262 for (;;)
3263 {
3264 HANDLE ahHandles[2] = { hProcWait, hParent };
3265 rcNt = NtWaitForMultipleObjects(2, &ahHandles[0], WaitAnyObject, TRUE /*Alertable*/, NULL /*pTimeout*/);
3266 if ( rcNt == STATUS_WAIT_0
3267 || rcNt == STATUS_WAIT_0 + 1
3268 || rcNt == STATUS_ABANDONED_WAIT_0
3269 || rcNt == STATUS_ABANDONED_WAIT_0 + 1)
3270 break;
3271 if ( rcNt != STATUS_TIMEOUT
3272 && rcNt != STATUS_USER_APC
3273 && rcNt != STATUS_ALERTED)
3274 supR3HardenedFatal("NtWaitForMultipleObjects returned %#x\n", rcNt);
3275 }
3276 NtClose(hParent);
3277 }
3278 else
3279 {
3280 /*
3281 * Wait for the process to terminate.
3282 */
3283 for (;;)
3284 {
3285 rcNt = NtWaitForSingleObject(hProcWait, TRUE /*Alertable*/, NULL /*pTimeout*/);
3286 if ( rcNt == STATUS_WAIT_0
3287 || rcNt == STATUS_ABANDONED_WAIT_0)
3288 break;
3289 if ( rcNt != STATUS_TIMEOUT
3290 && rcNt != STATUS_USER_APC
3291 && rcNt != STATUS_ALERTED)
3292 supR3HardenedFatal("NtWaitForSingleObject returned %#x\n", rcNt);
3293 }
3294 }
3295
3296 /*
3297 * Proxy the termination code of the child, if it exited already.
3298 */
3299 NTSTATUS rcNt2 = NtQueryInformationProcess(hProcWait, ProcessBasicInformation, &BasicInfo, sizeof(BasicInfo), NULL);
3300 if ( !NT_SUCCESS(rcNt2)
3301 || BasicInfo.ExitStatus == STATUS_PENDING)
3302 BasicInfo.ExitStatus = RTEXITCODE_FAILURE;
3303
3304 NtClose(hProcWait);
3305 SUP_DPRINTF(("supR3HardenedWinDoReSpawn(%d): Quitting: ExitCode=%#x rcNt=%#x\n", iWhich, BasicInfo.ExitStatus, rcNt));
3306 suplibHardenedExit((RTEXITCODE)BasicInfo.ExitStatus);
3307}
3308
3309
3310/**
3311 * Checks if the driver exists.
3312 *
3313 * This checks whether the driver is present in the /Driver object directory.
3314 * Drivers being initialized or terminated will have an object there
3315 * before/after their devices nodes are created/deleted.
3316 *
3317 * @returns true if it exists, false if not.
3318 * @param pszDriver The driver name.
3319 */
3320static bool supR3HardenedWinDriverExists(const char *pszDriver)
3321{
3322 /*
3323 * Open the driver object directory.
3324 */
3325 UNICODE_STRING NtDirName;
3326 NtDirName.Buffer = L"\\Driver";
3327 NtDirName.MaximumLength = sizeof(L"\\Driver");
3328 NtDirName.Length = NtDirName.MaximumLength - sizeof(WCHAR);
3329
3330 OBJECT_ATTRIBUTES ObjAttr;
3331 InitializeObjectAttributes(&ObjAttr, &NtDirName, OBJ_CASE_INSENSITIVE, NULL /*hRootDir*/, NULL /*pSecDesc*/);
3332
3333 HANDLE hDir;
3334 NTSTATUS rcNt = NtOpenDirectoryObject(&hDir, DIRECTORY_QUERY | FILE_LIST_DIRECTORY, &ObjAttr);
3335#ifdef VBOX_STRICT
3336 SUPR3HARDENED_ASSERT_NT_SUCCESS(rcNt);
3337#endif
3338 if (!NT_SUCCESS(rcNt))
3339 return true;
3340
3341 /*
3342 * Enumerate it, looking for the driver.
3343 */
3344 bool fFound = true;
3345 ULONG uObjDirCtx = 0;
3346 do
3347 {
3348 uint32_t abBuffer[_64K + _1K];
3349 ULONG cbActual;
3350 rcNt = NtQueryDirectoryObject(hDir,
3351 abBuffer,
3352 sizeof(abBuffer) - 4, /* minus four for string terminator space. */
3353 FALSE /*ReturnSingleEntry */,
3354 FALSE /*RestartScan*/,
3355 &uObjDirCtx,
3356 &cbActual);
3357 if (!NT_SUCCESS(rcNt) || cbActual < sizeof(OBJECT_DIRECTORY_INFORMATION))
3358 break;
3359
3360 POBJECT_DIRECTORY_INFORMATION pObjDir = (POBJECT_DIRECTORY_INFORMATION)abBuffer;
3361 while (pObjDir->Name.Length != 0)
3362 {
3363 WCHAR wcSaved = pObjDir->Name.Buffer[pObjDir->Name.Length / sizeof(WCHAR)];
3364 pObjDir->Name.Buffer[pObjDir->Name.Length / sizeof(WCHAR)] = '\0';
3365 if ( pObjDir->Name.Length > 1
3366 && RTUtf16ICmpAscii(pObjDir->Name.Buffer, pszDriver) == 0)
3367 {
3368 fFound = true;
3369 break;
3370 }
3371 pObjDir->Name.Buffer[pObjDir->Name.Length / sizeof(WCHAR)] = wcSaved;
3372
3373 /* Next directory entry. */
3374 pObjDir++;
3375 }
3376 } while (!fFound);
3377
3378 /*
3379 * Clean up and return.
3380 */
3381 NtClose(hDir);
3382
3383 return fFound;
3384}
3385
3386
3387/**
3388 * Open the stub device before the 2nd respawn.
3389 */
3390static void supR3HardenedWinOpenStubDevice(void)
3391{
3392 /*
3393 * Retry if we think driver might still be initializing (STATUS_NO_SUCH_DEVICE + \Drivers\VBoxDrv).
3394 */
3395 static const WCHAR s_wszName[] = L"\\Device\\VBoxDrvStub";
3396 DWORD const uStartTick = GetTickCount();
3397 NTSTATUS rcNt;
3398 uint32_t iTry;
3399
3400 for (iTry = 0;; iTry++)
3401 {
3402 HANDLE hFile = RTNT_INVALID_HANDLE_VALUE;
3403 IO_STATUS_BLOCK Ios = RTNT_IO_STATUS_BLOCK_INITIALIZER;
3404
3405 UNICODE_STRING NtName;
3406 NtName.Buffer = (PWSTR)s_wszName;
3407 NtName.Length = sizeof(s_wszName) - sizeof(WCHAR);
3408 NtName.MaximumLength = sizeof(s_wszName);
3409
3410 OBJECT_ATTRIBUTES ObjAttr;
3411 InitializeObjectAttributes(&ObjAttr, &NtName, OBJ_CASE_INSENSITIVE, NULL /*hRootDir*/, NULL /*pSecDesc*/);
3412
3413 rcNt = NtCreateFile(&hFile,
3414 GENERIC_READ | GENERIC_WRITE,
3415 &ObjAttr,
3416 &Ios,
3417 NULL /* Allocation Size*/,
3418 FILE_ATTRIBUTE_NORMAL,
3419 FILE_SHARE_READ | FILE_SHARE_WRITE,
3420 FILE_OPEN,
3421 FILE_NON_DIRECTORY_FILE,
3422 NULL /*EaBuffer*/,
3423 0 /*EaLength*/);
3424 if (NT_SUCCESS(rcNt))
3425 rcNt = Ios.Status;
3426
3427 /* The STATUS_NO_SUCH_DEVICE might be returned if the device is not
3428 completely initialized. Delay a little bit and try again. */
3429 if (rcNt != STATUS_NO_SUCH_DEVICE)
3430 break;
3431 if (iTry > 0 && GetTickCount() - uStartTick > 5000) /* 5 sec, at least two tries */
3432 break;
3433 if (!supR3HardenedWinDriverExists("VBoxDrv"))
3434 {
3435 /** @todo Consider starting the VBoxdrv.sys service. Requires 2nd process
3436 * though, rather complicated actually as CreateProcess causes all
3437 * kind of things to happen to this process which would make it hard to
3438 * pass the process verification tests... :-/ */
3439 break;
3440 }
3441
3442 LARGE_INTEGER Time;
3443 if (iTry < 8)
3444 Time.QuadPart = -1000000 / 100; /* 1ms in 100ns units, relative time. */
3445 else
3446 Time.QuadPart = -32000000 / 100; /* 32ms in 100ns units, relative time. */
3447 NtDelayExecution(TRUE, &Time);
3448 }
3449
3450 if (!NT_SUCCESS(rcNt))
3451 {
3452 int rc = VERR_OPEN_FAILED;
3453 if (SUP_NT_STATUS_IS_VBOX(rcNt)) /* See VBoxDrvNtErr2NtStatus. */
3454 rc = SUP_NT_STATUS_TO_VBOX(rcNt);
3455 else
3456 {
3457 const char *pszDefine;
3458 switch (rcNt)
3459 {
3460 case STATUS_NO_SUCH_DEVICE: pszDefine = " STATUS_NO_SUCH_DEVICE"; break;
3461 case STATUS_OBJECT_NAME_NOT_FOUND: pszDefine = " STATUS_OBJECT_NAME_NOT_FOUND"; break;
3462 case STATUS_ACCESS_DENIED: pszDefine = " STATUS_ACCESS_DENIED"; break;
3463 case STATUS_TRUST_FAILURE: pszDefine = " STATUS_TRUST_FAILURE"; break;
3464 default: pszDefine = ""; break;
3465 }
3466 supR3HardenedFatalMsg("supR3HardenedWinReSpawn", kSupInitOp_Driver, VERR_OPEN_FAILED,
3467 "NtCreateFile(%ls) failed: %#x%s (%u retries)\n", s_wszName, rcNt, pszDefine, iTry);
3468 }
3469 supR3HardenedFatalMsg("supR3HardenedWinReSpawn", kSupInitOp_Driver, rc,
3470 "NtCreateFile(%ls) failed: %Rrc (rcNt=%#x)\n", s_wszName, rc, rcNt);
3471 }
3472}
3473
3474
3475/**
3476 * Called by the main code if supR3HardenedWinIsReSpawnNeeded returns @c true.
3477 *
3478 * @returns Program exit code.
3479 */
3480DECLHIDDEN(int) supR3HardenedWinReSpawn(int iWhich)
3481{
3482 /*
3483 * Before the 2nd respawn we set up a child protection deal with the
3484 * support driver via /Devices/VBoxDrvStub.
3485 */
3486 if (iWhich == 2)
3487 supR3HardenedWinOpenStubDevice();
3488
3489 /*
3490 * Respawn the process with kernel protection for the new process.
3491 */
3492 return supR3HardenedWinDoReSpawn(iWhich);
3493}
3494
3495
3496/**
3497 * Checks if re-spawning is required, replacing the respawn argument if not.
3498 *
3499 * @returns true if required, false if not. In the latter case, the first
3500 * argument in the vector is replaced.
3501 * @param iWhich Which respawn we're to check for, 1 being the
3502 * first one, and 2 the second and final.
3503 * @param cArgs The number of arguments.
3504 * @param papszArgs Pointer to the argument vector.
3505 */
3506DECLHIDDEN(bool) supR3HardenedWinIsReSpawnNeeded(int iWhich, int cArgs, char **papszArgs)
3507{
3508 SUPR3HARDENED_ASSERT(g_cSuplibHardenedWindowsMainCalls == 1);
3509 SUPR3HARDENED_ASSERT(iWhich == 1 || iWhich == 2);
3510
3511 if (cArgs < 1)
3512 return true;
3513
3514 if (suplibHardenedStrCmp(papszArgs[0], SUPR3_RESPAWN_1_ARG0) == 0)
3515 {
3516 if (iWhich > 1)
3517 return true;
3518 }
3519 else if (suplibHardenedStrCmp(papszArgs[0], SUPR3_RESPAWN_2_ARG0) == 0)
3520 {
3521 if (iWhich < 2)
3522 return false;
3523 }
3524 else
3525 return true;
3526
3527 /* Replace the argument. */
3528 papszArgs[0] = g_szSupLibHardenedExePath;
3529 return false;
3530}
3531
3532
3533/**
3534 * Initializes the windows verficiation bits.
3535 * @param fFlags The main flags.
3536 */
3537DECLHIDDEN(void) supR3HardenedWinInit(uint32_t fFlags)
3538{
3539 RTErrInfoInitStatic(&g_ErrInfoStatic);
3540 int rc = supHardenedWinInitImageVerifier(&g_ErrInfoStatic.Core);
3541 if (RT_FAILURE(rc))
3542 supR3HardenedFatalMsg("supR3HardenedWinInit", kSupInitOp_Misc, rc,
3543 "supHardenedWinInitImageVerifier failed: %s", g_ErrInfoStatic.szMsg);
3544
3545 if (!(fFlags & SUPSECMAIN_FLAGS_DONT_OPEN_DEV))
3546 {
3547 /* Do a self purification to cure avast's weird NtOpenFile write-thru
3548 change in GetBinaryTypeW change in kernel32. */
3549 supHardenedWinVerifyProcess(NtCurrentProcess(), NtCurrentThread(), SUPHARDNTVPKIND_SELF_PURIFICATION, NULL);
3550
3551 supR3HardenedWinInstallHooks();
3552 }
3553
3554#ifndef VBOX_WITH_VISTA_NO_SP
3555 /*
3556 * Complain about Vista w/o service pack if we're launching a VM.
3557 */
3558 if ( !(fFlags & SUPSECMAIN_FLAGS_DONT_OPEN_DEV)
3559 && g_uNtVerCombined >= SUP_NT_VER_VISTA
3560 && g_uNtVerCombined < SUP_MAKE_NT_VER_COMBINED(6, 0, 6001, 0, 0))
3561 supR3HardenedFatalMsg("supR3HardenedWinInit", kSupInitOp_Misc, VERR_NOT_SUPPORTED,
3562 "Window Vista without any service pack installed is not supported. Please install the latest service pack.");
3563#endif
3564}
3565
3566
3567/**
3568 * Converts the Windows command line string (UTF-16) to an array of UTF-8
3569 * arguments suitable for passing to main().
3570 *
3571 * @returns Pointer to the argument array.
3572 * @param pawcCmdLine The UTF-16 windows command line to parse.
3573 * @param cwcCmdLine The length of the command line.
3574 * @param pcArgs Where to return the number of arguments.
3575 */
3576static char **suplibCommandLineToArgvWStub(PCRTUTF16 pawcCmdLine, size_t cwcCmdLine, int *pcArgs)
3577{
3578 /*
3579 * Convert the command line string to UTF-8.
3580 */
3581 char *pszCmdLine = NULL;
3582 SUPR3HARDENED_ASSERT(RT_SUCCESS(RTUtf16ToUtf8Ex(pawcCmdLine, cwcCmdLine, &pszCmdLine, 0, NULL)));
3583
3584 /*
3585 * Parse the command line, carving argument strings out of it.
3586 */
3587 int cArgs = 0;
3588 int cArgsAllocated = 4;
3589 char **papszArgs = (char **)suplibHardenedAllocZ(sizeof(char *) * cArgsAllocated);
3590 char *pszSrc = pszCmdLine;
3591 for (;;)
3592 {
3593 /* skip leading blanks. */
3594 char ch = *pszSrc;
3595 while (suplibCommandLineIsArgSeparator(ch))
3596 ch = *++pszSrc;
3597 if (!ch)
3598 break;
3599
3600 /* Add argument to the vector. */
3601 if (cArgs + 2 >= cArgsAllocated)
3602 {
3603 cArgsAllocated *= 2;
3604 papszArgs = (char **)suplibHardenedReAlloc(papszArgs, sizeof(char *) * cArgsAllocated);
3605 }
3606 papszArgs[cArgs++] = pszSrc;
3607 papszArgs[cArgs] = NULL;
3608
3609 /* Unquote and unescape the string. */
3610 char *pszDst = pszSrc++;
3611 bool fQuoted = false;
3612 do
3613 {
3614 if (ch == '"')
3615 fQuoted = !fQuoted;
3616 else if (ch != '\\' || (*pszSrc != '\\' && *pszSrc != '"'))
3617 *pszDst++ = ch;
3618 else
3619 {
3620 unsigned cSlashes = 0;
3621 while ((ch = *pszSrc++) == '\\')
3622 cSlashes++;
3623 if (ch == '"')
3624 {
3625 while (cSlashes >= 2)
3626 {
3627 cSlashes -= 2;
3628 *pszDst++ = '\\';
3629 }
3630 if (cSlashes)
3631 *pszDst++ = '"';
3632 else
3633 fQuoted = !fQuoted;
3634 }
3635 else
3636 {
3637 pszSrc--;
3638 while (cSlashes-- > 0)
3639 *pszDst++ = '\\';
3640 }
3641 }
3642
3643 ch = *pszSrc++;
3644 } while (ch != '\0' && (fQuoted || !suplibCommandLineIsArgSeparator(ch)));
3645
3646 /* Terminate the argument. */
3647 *pszDst = '\0';
3648 if (!ch)
3649 break;
3650 }
3651
3652 *pcArgs = cArgs;
3653 return papszArgs;
3654}
3655
3656
3657extern "C" int main(int argc, char **argv, char **envp);
3658
3659/**
3660 * The executable entry point.
3661 *
3662 * This is normally taken care of by the C runtime library, but we don't want to
3663 * get involved with anything as complicated like the CRT in this setup. So, we
3664 * it everything ourselves, including parameter parsing.
3665 */
3666extern "C" void __stdcall suplibHardenedWindowsMain(void)
3667{
3668 RTEXITCODE rcExit = RTEXITCODE_FAILURE;
3669
3670 g_cSuplibHardenedWindowsMainCalls++;
3671
3672 /*
3673 * Initialize the NTDLL API wrappers. This aims at bypassing patched NTDLL
3674 * in all the processes leading up the VM process.
3675 */
3676 supR3HardenedWinInitImports();
3677
3678 /*
3679 * Init g_uNtVerCombined. (The code is shared with SUPR3.lib and lives in
3680 * SUPHardenedVerfiyImage-win.cpp.)
3681 */
3682 supR3HardenedWinInitVersion();
3683
3684 /*
3685 * Convert the arguments to UTF-8 and open the log file if specified.
3686 * This must be done as early as possible since the code below may fail.
3687 */
3688 PUNICODE_STRING pCmdLineStr = &NtCurrentPeb()->ProcessParameters->CommandLine;
3689 int cArgs;
3690 char **papszArgs = suplibCommandLineToArgvWStub(pCmdLineStr->Buffer, pCmdLineStr->Length / sizeof(WCHAR), &cArgs);
3691
3692 supR3HardenedOpenLog(&cArgs, papszArgs);
3693
3694 /*
3695 * Get the executable name.
3696 */
3697 DWORD cwcExecName = GetModuleFileNameW(GetModuleHandleW(NULL), g_wszSupLibHardenedExePath,
3698 RT_ELEMENTS(g_wszSupLibHardenedExePath));
3699 if (cwcExecName >= RT_ELEMENTS(g_wszSupLibHardenedExePath))
3700 supR3HardenedFatalMsg("suplibHardenedWindowsMain", kSupInitOp_Integrity, VERR_BUFFER_OVERFLOW,
3701 "The executable path is too long.");
3702
3703 /* The NT version. */
3704 HANDLE hFile = CreateFileW(g_wszSupLibHardenedExePath, GENERIC_READ, FILE_SHARE_READ, NULL /*pSecurityAttributes*/,
3705 OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL /*hTemplateFile*/);
3706 if (hFile == NULL || hFile == INVALID_HANDLE_VALUE)
3707 supR3HardenedFatalMsg("suplibHardenedWindowsMain", kSupInitOp_Integrity, RTErrConvertFromWin32(GetLastError()),
3708 "Error opening the executable: %u (%ls).", GetLastError());
3709 RT_ZERO(g_SupLibHardenedExeNtPath);
3710 ULONG cbIgn;
3711 NTSTATUS rcNt = NtQueryObject(hFile, ObjectNameInformation, &g_SupLibHardenedExeNtPath,
3712 sizeof(g_SupLibHardenedExeNtPath) - sizeof(WCHAR), &cbIgn);
3713 if (!NT_SUCCESS(rcNt))
3714 supR3HardenedFatalMsg("suplibHardenedWindowsMain", kSupInitOp_Integrity, RTErrConvertFromNtStatus(rcNt),
3715 "NtQueryObject -> %#x (on %ls)\n", rcNt, g_wszSupLibHardenedExePath);
3716 NtClose(hFile);
3717
3718 /* The NT executable name offset / dir path length. */
3719 g_offSupLibHardenedExeNtName = g_SupLibHardenedExeNtPath.UniStr.Length / sizeof(WCHAR);
3720 while ( g_offSupLibHardenedExeNtName > 1
3721 && g_SupLibHardenedExeNtPath.UniStr.Buffer[g_offSupLibHardenedExeNtName - 1] != '\\' )
3722 g_offSupLibHardenedExeNtName--;
3723
3724 /*
3725 * Call the C/C++ main function.
3726 */
3727 SUP_DPRINTF(("Calling main()\n"));
3728 rcExit = (RTEXITCODE)main(cArgs, papszArgs, NULL);
3729
3730 /*
3731 * Exit the process (never return).
3732 */
3733 SUP_DPRINTF(("Terminating the normal way: rcExit=%d\n", rcExit));
3734 suplibHardenedExit(rcExit);
3735}
3736
Note: See TracBrowser for help on using the repository browser.

© 2025 Oracle Support Privacy / Do Not Sell My Info Terms of Use Trademark Policy Automated Access Etiquette