VirtualBox

source: vbox/trunk/src/VBox/HostDrivers/Support/win/SUPHardenedVerifyProcess-win.cpp@ 106945

最後變更 在這個檔案從106945是 106945,由 vboxsync 提交於 4 月 前

SUPDrv,/Config.kmk,/Makefile.kmk: Implemented the simplified process validation for the VBOX_WITH_MINIMAL_HARDENING mode. jiraref:VBP-1449

  • 屬性 svn:eol-style 設為 native
  • 屬性 svn:keywords 設為 Author Date Id Revision
檔案大小: 117.2 KB
 
1/* $Id: SUPHardenedVerifyProcess-win.cpp 106945 2024-11-12 02:41:36Z vboxsync $ */
2/** @file
3 * VirtualBox Support Library/Driver - Hardened Process Verification, Windows.
4 */
5
6/*
7 * Copyright (C) 2006-2024 Oracle and/or its affiliates.
8 *
9 * This file is part of VirtualBox base platform packages, as
10 * available from https://www.alldomusa.eu.org.
11 *
12 * This program is free software; you can redistribute it and/or
13 * modify it under the terms of the GNU General Public License
14 * as published by the Free Software Foundation, in version 3 of the
15 * License.
16 *
17 * This program is distributed in the hope that it will be useful, but
18 * WITHOUT ANY WARRANTY; without even the implied warranty of
19 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
20 * General Public License for more details.
21 *
22 * You should have received a copy of the GNU General Public License
23 * along with this program; if not, see <https://www.gnu.org/licenses>.
24 *
25 * The contents of this file may alternatively be used under the terms
26 * of the Common Development and Distribution License Version 1.0
27 * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
28 * in the VirtualBox distribution, in which case the provisions of the
29 * CDDL are applicable instead of those of the GPL.
30 *
31 * You may elect to license modified versions of this file under the
32 * terms and conditions of either the GPL or the CDDL or both.
33 *
34 * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
35 */
36
37
38/*********************************************************************************************************************************
39* Header Files *
40*********************************************************************************************************************************/
41#ifdef IN_RING0
42# ifndef IPRT_NT_MAP_TO_ZW
43# define IPRT_NT_MAP_TO_ZW
44# endif
45# include <iprt/nt/nt.h>
46# include <ntimage.h>
47#else
48# include <iprt/nt/nt-and-windows.h>
49#endif
50
51#include <VBox/sup.h>
52#include <VBox/err.h>
53#include <iprt/alloca.h>
54#include <iprt/ctype.h>
55#include <iprt/param.h>
56#include <iprt/string.h>
57#include <iprt/utf16.h>
58#include <iprt/zero.h>
59
60#ifdef IN_RING0
61# include "SUPDrvInternal.h"
62#else
63# include "SUPLibInternal.h"
64#endif
65#include "win/SUPHardenedVerify-win.h"
66
67
68/*********************************************************************************************************************************
69* Defined Constants And Macros *
70*********************************************************************************************************************************/
71#if defined(RT_ARCH_ARM64)
72AssertCompile(PAGE_SIZE == _4K);
73# define g_abRTZeroPage g_abRTZero4K
74#endif
75
76#ifdef VBOX_WITH_MINIMAL_HARDENING
77# define IMAGE_LOG_NAME_FMT "%ls"
78# define IMAGE_LOG_NAME(pImage) ((pImage)->Name.awcBuffer)
79#else
80# define IMAGE_LOG_NAME_FMT "%s"
81# define IMAGE_LOG_NAME(pImage) ((pImage)->pszName)
82#endif
83
84
85/*********************************************************************************************************************************
86* Structures and Typedefs *
87*********************************************************************************************************************************/
88/**
89 * Virtual address space region.
90 */
91typedef struct SUPHNTVPREGION
92{
93 /** The RVA of the region. */
94 uint32_t uRva;
95 /** The size of the region. */
96 uint32_t cb;
97 /** The protection of the region. */
98 uint32_t fProt;
99} SUPHNTVPREGION;
100/** Pointer to a virtual address space region. */
101typedef SUPHNTVPREGION *PSUPHNTVPREGION;
102
103/**
104 * Virtual address space image information.
105 */
106typedef struct SUPHNTVPIMAGE
107{
108 /** The base address of the image. */
109 uintptr_t uImageBase;
110 /** The size of the image mapping. */
111 uintptr_t cbImage;
112
113#ifndef VBOX_WITH_MINIMAL_HARDENING
114 /** The name from the allowed lists. */
115 const char *pszName;
116#endif
117 /** Name structure for NtQueryVirtualMemory/MemorySectionName. */
118 struct
119 {
120 /** The full unicode name. */
121 UNICODE_STRING UniStr;
122 /** Buffer space. */
123 WCHAR awcBuffer[260];
124 } Name;
125
126 /** The number of mapping regions. */
127 uint32_t cRegions;
128 /** Mapping regions. */
129 SUPHNTVPREGION aRegions[16];
130
131 /** The image characteristics from the FileHeader. */
132 uint16_t fImageCharecteristics;
133 /** The DLL characteristics from the OptionalHeader. */
134 uint16_t fDllCharecteristics;
135
136 /** Set if this is the DLL. */
137 bool fDll;
138 /** Set if the image is NTDLL and the verficiation code needs to watch out for
139 * the NtCreateSection patch. */
140 bool fNtCreateSectionPatch;
141 /** Whether the API set schema hack needs to be applied when verifying memory
142 * content. The hack means that we only check if the 1st section is mapped. */
143 bool fApiSetSchemaOnlySection1;
144 /** This may be a 32-bit resource DLL. */
145 bool f32bitResourceDll;
146
147 /** Pointer to the loader cache entry for the image. */
148 PSUPHNTLDRCACHEENTRY pCacheEntry;
149#ifdef IN_RING0
150 /** In ring-0 we don't currently cache images, so put it here. */
151 SUPHNTLDRCACHEENTRY CacheEntry;
152#endif
153} SUPHNTVPIMAGE;
154/** Pointer to image info from the virtual address space scan. */
155typedef SUPHNTVPIMAGE *PSUPHNTVPIMAGE;
156
157/**
158 * Virtual address space scanning state.
159 */
160typedef struct SUPHNTVPSTATE
161{
162 /** Type of verification to perform. */
163 SUPHARDNTVPKIND enmKind;
164 /** Combination of SUPHARDNTVP_F_XXX. */
165 uint32_t fFlags;
166 /** The result. */
167 int rcResult;
168 /** Number of fixes we've done.
169 * Only applicable in the purification modes. */
170 uint32_t cFixes;
171 /** Number of images in aImages. */
172 uint32_t cImages;
173 /** The index of the last image we looked up. */
174 uint32_t iImageHint;
175 /** The process handle. */
176 HANDLE hProcess;
177 /** Images found in the process.
178 * The array is large enough to hold the executable, all allowed DLLs, and one
179 * more so we can get the image name of the first unwanted DLL. */
180 SUPHNTVPIMAGE aImages[1 + 6 + 1
181#ifdef VBOX_PERMIT_VERIFIER_DLL
182 + 1
183#endif
184#ifdef VBOX_PERMIT_MORE
185 + 5
186#endif
187#ifdef VBOX_PERMIT_VISUAL_STUDIO_PROFILING
188 + 16
189#endif
190 ];
191 /** Memory compare scratch buffer.*/
192 uint8_t abMemory[_4K];
193 /** File compare scratch buffer.*/
194 uint8_t abFile[_4K];
195 /** Section headers for use when comparing file and loaded image. */
196 IMAGE_SECTION_HEADER aSecHdrs[16];
197 /** Pointer to the error info. */
198 PRTERRINFO pErrInfo;
199} SUPHNTVPSTATE;
200/** Pointer to stat information of a virtual address space scan. */
201typedef SUPHNTVPSTATE *PSUPHNTVPSTATE;
202
203
204/*********************************************************************************************************************************
205* Global Variables *
206*********************************************************************************************************************************/
207/**
208 * System DLLs allowed to be loaded into the process.
209 * @remarks supHardNtVpCheckDlls assumes these are lower case.
210 */
211static const char *g_apszSupNtVpAllowedDlls[] =
212{
213 "ntdll.dll",
214 "kernel32.dll",
215 "kernelbase.dll",
216 "apphelp.dll",
217 "apisetschema.dll",
218#ifdef VBOX_PERMIT_VERIFIER_DLL
219 "verifier.dll",
220#endif
221#ifdef VBOX_PERMIT_MORE
222# define VBOX_PERMIT_MORE_FIRST_IDX 5
223 "sfc.dll",
224 "sfc_os.dll",
225 "user32.dll",
226 "acres.dll",
227 "acgenral.dll",
228#endif
229#ifdef VBOX_PERMIT_VISUAL_STUDIO_PROFILING
230 "psapi.dll",
231 "msvcrt.dll",
232 "advapi32.dll",
233 "sechost.dll",
234 "rpcrt4.dll",
235 "SamplingRuntime.dll",
236#endif
237};
238
239/**
240 * VBox executables allowed to start VMs.
241 * @remarks Remember to keep in sync with g_aSupInstallFiles in
242 * SUPR3HardenedVerify.cpp.
243 */
244static const char *g_apszSupNtVpAllowedVmExes[] =
245{
246 "VBoxHeadless.exe",
247 "VirtualBoxVM.exe",
248 "VBoxSDL.exe",
249 "VBoxNetDHCP.exe",
250 "VBoxNetNAT.exe",
251 "VBoxVMMPreload.exe",
252
253 "tstMicro.exe",
254 "tstPDMAsyncCompletion.exe",
255 "tstPDMAsyncCompletionStress.exe",
256 "tstVMM.exe",
257 "tstVMREQ.exe",
258 "tstCFGM.exe",
259 "tstGIP-2.exe",
260 "tstIntNet-1.exe",
261 "tstMMHyperHeap.exe",
262 "tstRTR0ThreadPreemptionDriver.exe",
263 "tstRTR0MemUserKernelDriver.exe",
264 "tstRTR0SemMutexDriver.exe",
265 "tstRTR0TimerDriver.exe",
266 "tstSSM.exe",
267 "tstInt.exe",
268};
269
270/** Pointer to NtQueryVirtualMemory. Initialized by SUPDrv-win.cpp in
271 * ring-0, in ring-3 it's just a slightly confusing define. */
272#ifdef IN_RING0
273PFNNTQUERYVIRTUALMEMORY g_pfnNtQueryVirtualMemory = NULL;
274#else
275# define g_pfnNtQueryVirtualMemory NtQueryVirtualMemory
276#endif
277
278#ifdef IN_RING3
279/** The number of valid entries in the loader cache. */
280static uint32_t g_cSupNtVpLdrCacheEntries = 0;
281/** The loader cache entries. */
282static SUPHNTLDRCACHEENTRY g_aSupNtVpLdrCacheEntries[RT_ELEMENTS(g_apszSupNtVpAllowedDlls) + 1 + 3];
283#endif
284
285
286/**
287 * Fills in error information.
288 *
289 * @returns @a rc.
290 * @param pErrInfo Pointer to the extended error info structure.
291 * Can be NULL.
292 * @param rc The status to return.
293 * @param pszMsg The format string for the message.
294 * @param ... The arguments for the format string.
295 */
296static int supHardNtVpSetInfo1(PRTERRINFO pErrInfo, int rc, const char *pszMsg, ...)
297{
298 va_list va;
299#ifdef IN_RING3
300 va_start(va, pszMsg);
301 supR3HardenedError(rc, false /*fFatal*/, "%N\n", pszMsg, &va);
302 va_end(va);
303#endif
304
305 va_start(va, pszMsg);
306 RTErrInfoSetV(pErrInfo, rc, pszMsg, va);
307 va_end(va);
308
309 return rc;
310}
311
312
313/**
314 * Adds error information.
315 *
316 * @returns @a rc.
317 * @param pErrInfo Pointer to the extended error info structure
318 * which may contain some details already. Can be
319 * NULL.
320 * @param rc The status to return.
321 * @param pszMsg The format string for the message.
322 * @param ... The arguments for the format string.
323 */
324static int supHardNtVpAddInfo1(PRTERRINFO pErrInfo, int rc, const char *pszMsg, ...)
325{
326 va_list va;
327#ifdef IN_RING3
328 va_start(va, pszMsg);
329 if (pErrInfo && pErrInfo->pszMsg)
330 supR3HardenedError(rc, false /*fFatal*/, "%N - %s\n", pszMsg, &va, pErrInfo->pszMsg);
331 else
332 supR3HardenedError(rc, false /*fFatal*/, "%N\n", pszMsg, &va);
333 va_end(va);
334#endif
335
336 va_start(va, pszMsg);
337 RTErrInfoAddV(pErrInfo, rc, pszMsg, va);
338 va_end(va);
339
340 return rc;
341}
342
343
344/**
345 * Fills in error information.
346 *
347 * @returns @a rc.
348 * @param pThis The process validator instance.
349 * @param rc The status to return.
350 * @param pszMsg The format string for the message.
351 * @param ... The arguments for the format string.
352 */
353static int supHardNtVpSetInfo2(PSUPHNTVPSTATE pThis, int rc, const char *pszMsg, ...)
354{
355 va_list va;
356#ifdef IN_RING3
357 va_start(va, pszMsg);
358 supR3HardenedError(rc, false /*fFatal*/, "%N\n", pszMsg, &va);
359 va_end(va);
360#endif
361
362 va_start(va, pszMsg);
363#ifdef IN_RING0
364 RTErrInfoSetV(pThis->pErrInfo, rc, pszMsg, va);
365 pThis->rcResult = rc;
366#else
367 if (RT_SUCCESS(pThis->rcResult))
368 {
369 RTErrInfoSetV(pThis->pErrInfo, rc, pszMsg, va);
370 pThis->rcResult = rc;
371 }
372 else
373 {
374 RTErrInfoAddF(pThis->pErrInfo, rc, " \n[rc=%d] ", rc);
375 RTErrInfoAddV(pThis->pErrInfo, rc, pszMsg, va);
376 }
377#endif
378 va_end(va);
379
380 return pThis->rcResult;
381}
382
383
384static int supHardNtVpReadImage(PSUPHNTVPIMAGE pImage, uint64_t off, void *pvBuf, size_t cbRead)
385{
386 return pImage->pCacheEntry->pNtViRdr->Core.pfnRead(&pImage->pCacheEntry->pNtViRdr->Core, pvBuf, cbRead, off);
387}
388
389
390static NTSTATUS supHardNtVpReadMem(HANDLE hProcess, uintptr_t uPtr, void *pvBuf, size_t cbRead)
391{
392#ifdef IN_RING0
393 /* ASSUMES hProcess is the current process. */
394 RT_NOREF1(hProcess);
395 /** @todo use MmCopyVirtualMemory where available! */
396 int rc = RTR0MemUserCopyFrom(pvBuf, uPtr, cbRead);
397 if (RT_SUCCESS(rc))
398 return STATUS_SUCCESS;
399 return STATUS_ACCESS_DENIED;
400#else
401 SIZE_T cbIgn;
402 NTSTATUS rcNt = NtReadVirtualMemory(hProcess, (PVOID)uPtr, pvBuf, cbRead, &cbIgn);
403 if (NT_SUCCESS(rcNt) && cbIgn != cbRead)
404 rcNt = STATUS_IO_DEVICE_ERROR;
405 return rcNt;
406#endif
407}
408
409
410#ifdef IN_RING3
411static NTSTATUS supHardNtVpFileMemRestore(PSUPHNTVPSTATE pThis, PVOID pvRestoreAddr, uint8_t const *pbFile, uint32_t cbToRestore,
412 uint32_t fCorrectProtection)
413{
414 PVOID pvProt = pvRestoreAddr;
415 SIZE_T cbProt = cbToRestore;
416 ULONG fOldProt = 0;
417 NTSTATUS rcNt = NtProtectVirtualMemory(pThis->hProcess, &pvProt, &cbProt, PAGE_READWRITE, &fOldProt);
418 if (NT_SUCCESS(rcNt))
419 {
420 SIZE_T cbIgnored;
421 rcNt = NtWriteVirtualMemory(pThis->hProcess, pvRestoreAddr, pbFile, cbToRestore, &cbIgnored);
422
423 pvProt = pvRestoreAddr;
424 cbProt = cbToRestore;
425 NTSTATUS rcNt2 = NtProtectVirtualMemory(pThis->hProcess, &pvProt, &cbProt, fCorrectProtection, &fOldProt);
426 if (NT_SUCCESS(rcNt))
427 rcNt = rcNt2;
428 }
429 pThis->cFixes++;
430 return rcNt;
431}
432#endif /* IN_RING3 */
433
434
435typedef struct SUPHNTVPSKIPAREA
436{
437 uint32_t uRva;
438 uint32_t cb;
439} SUPHNTVPSKIPAREA;
440typedef SUPHNTVPSKIPAREA *PSUPHNTVPSKIPAREA;
441
442static int supHardNtVpFileMemCompareSection(PSUPHNTVPSTATE pThis, PSUPHNTVPIMAGE pImage,
443 uint32_t uRva, uint32_t cb, const uint8_t *pbFile,
444 int32_t iSh, PSUPHNTVPSKIPAREA paSkipAreas, uint32_t cSkipAreas,
445 uint32_t fCorrectProtection)
446{
447#ifndef IN_RING3
448 RT_NOREF1(fCorrectProtection);
449#endif
450 AssertCompileAdjacentMembers(SUPHNTVPSTATE, abMemory, abFile); /* Use both the memory and file buffers here. Parfait might hate me for this... */
451 uint32_t const cbMemory = sizeof(pThis->abMemory) + sizeof(pThis->abFile);
452 uint8_t * const pbMemory = &pThis->abMemory[0];
453
454 while (cb > 0)
455 {
456 uint32_t cbThis = RT_MIN(cb, cbMemory);
457
458 /* Clipping. */
459 uint32_t uNextRva = uRva + cbThis;
460 if (cSkipAreas)
461 {
462 uint32_t uRvaEnd = uNextRva;
463 uint32_t i = cSkipAreas;
464 while (i-- > 0)
465 {
466 uint32_t uSkipEnd = paSkipAreas[i].uRva + paSkipAreas[i].cb;
467 if ( uRva < uSkipEnd
468 && uRvaEnd > paSkipAreas[i].uRva)
469 {
470 if (uRva < paSkipAreas[i].uRva)
471 {
472 cbThis = paSkipAreas[i].uRva - uRva;
473 uRvaEnd = paSkipAreas[i].uRva;
474 uNextRva = uSkipEnd;
475 }
476 else if (uRvaEnd >= uSkipEnd)
477 {
478 cbThis -= uSkipEnd - uRva;
479 pbFile += uSkipEnd - uRva;
480 uRva = uSkipEnd;
481 }
482 else
483 {
484 uNextRva = uSkipEnd;
485 cbThis = 0;
486 break;
487 }
488 }
489 }
490 }
491
492 /* Read the memory. */
493 NTSTATUS rcNt = supHardNtVpReadMem(pThis->hProcess, pImage->uImageBase + uRva, pbMemory, cbThis);
494 if (!NT_SUCCESS(rcNt))
495 return supHardNtVpSetInfo2(pThis, VERR_SUP_VP_MEMORY_READ_ERROR,
496 IMAGE_LOG_NAME_FMT ": Error reading %#x bytes at %p (rva %#x, #%u, %.8s) from memory: %#x",
497 IMAGE_LOG_NAME(pImage), cbThis, pImage->uImageBase + uRva, uRva, iSh + 1,
498 iSh >= 0 ? (char *)pThis->aSecHdrs[iSh].Name : "headers", rcNt);
499
500 /* Do the compare. */
501 if (memcmp(pbFile, pbMemory, cbThis) != 0)
502 {
503 const char *pachSectNm = iSh >= 0 ? (char *)pThis->aSecHdrs[iSh].Name : "headers";
504 SUP_DPRINTF((IMAGE_LOG_NAME_FMT ": Differences in section #%u (%s) between file and memory:\n",
505 IMAGE_LOG_NAME(pImage), iSh + 1, pachSectNm));
506
507 uint32_t off = 0;
508 while (off < cbThis && pbFile[off] == pbMemory[off])
509 off++;
510 SUP_DPRINTF((" %p / %#09x: %02x != %02x\n",
511 pImage->uImageBase + uRva + off, uRva + off, pbFile[off], pbMemory[off]));
512 uint32_t offLast = off;
513 uint32_t cDiffs = 1;
514 for (uint32_t off2 = off + 1; off2 < cbThis; off2++)
515 if (pbFile[off2] != pbMemory[off2])
516 {
517 SUP_DPRINTF((" %p / %#09x: %02x != %02x\n",
518 pImage->uImageBase + uRva + off2, uRva + off2, pbFile[off2], pbMemory[off2]));
519 cDiffs++;
520 offLast = off2;
521 }
522
523#ifdef IN_RING3
524 if ( pThis->enmKind == SUPHARDNTVPKIND_CHILD_PURIFICATION
525 || pThis->enmKind == SUPHARDNTVPKIND_SELF_PURIFICATION
526 || pThis->enmKind == SUPHARDNTVPKIND_SELF_PURIFICATION_LIMITED)
527 {
528 PVOID pvRestoreAddr = (uint8_t *)pImage->uImageBase + uRva;
529 rcNt = supHardNtVpFileMemRestore(pThis, pvRestoreAddr, pbFile, cbThis, fCorrectProtection);
530 if (NT_SUCCESS(rcNt))
531 SUP_DPRINTF((" Restored %#x bytes of original file content at %p\n", cbThis, pvRestoreAddr));
532 else
533 return supHardNtVpSetInfo2(pThis, VERR_SUP_VP_MEMORY_VS_FILE_MISMATCH,
534 IMAGE_LOG_NAME_FMT ": Failed to restore %#x bytes at %p (%#x, #%u, %s): %#x (cDiffs=%#x, first=%#x)",
535 IMAGE_LOG_NAME(pImage), cbThis, pvRestoreAddr, uRva, iSh + 1, pachSectNm, rcNt,
536 cDiffs, uRva + off);
537 }
538 else
539#endif /* IN_RING3 */
540 return supHardNtVpSetInfo2(pThis, VERR_SUP_VP_MEMORY_VS_FILE_MISMATCH,
541 IMAGE_LOG_NAME_FMT ": %u differences between %#x and %#x in #%u (%.8s), first: %02x != %02x",
542 IMAGE_LOG_NAME(pImage), cDiffs, uRva + off, uRva + offLast, iSh + 1,
543 pachSectNm, pbFile[off], pbMemory[off]);
544 }
545
546 /* Advance. The clipping makes it a little bit complicated. */
547 cbThis = uNextRva - uRva;
548 if (cbThis >= cb)
549 break;
550 cb -= cbThis;
551 pbFile += cbThis;
552 uRva = uNextRva;
553 }
554 return VINF_SUCCESS;
555}
556
557
558
559static int supHardNtVpCheckSectionProtection(PSUPHNTVPSTATE pThis, PSUPHNTVPIMAGE pImage,
560 uint32_t uRva, uint32_t cb, uint32_t fProt)
561{
562 uint32_t const cbOrg = cb;
563 if (!cb)
564 return VINF_SUCCESS;
565 if ( pThis->enmKind == SUPHARDNTVPKIND_CHILD_PURIFICATION
566 || pThis->enmKind == SUPHARDNTVPKIND_SELF_PURIFICATION
567 || pThis->enmKind == SUPHARDNTVPKIND_SELF_PURIFICATION_LIMITED)
568 return VINF_SUCCESS;
569
570 for (uint32_t i = 0; i < pImage->cRegions; i++)
571 {
572 uint32_t offRegion = uRva - pImage->aRegions[i].uRva;
573 if (offRegion < pImage->aRegions[i].cb)
574 {
575 uint32_t cbLeft = pImage->aRegions[i].cb - offRegion;
576 if ( pImage->aRegions[i].fProt != fProt
577 && ( fProt != PAGE_READWRITE
578 || pImage->aRegions[i].fProt != PAGE_WRITECOPY))
579 return supHardNtVpSetInfo2(pThis, VERR_SUP_VP_SECTION_PROTECTION_MISMATCH,
580 IMAGE_LOG_NAME_FMT ": RVA range %#x-%#x protection is %#x, expected %#x. (cb=%#x)",
581 IMAGE_LOG_NAME(pImage), uRva, uRva + cbLeft - 1, pImage->aRegions[i].fProt, fProt, cb);
582 if (cbLeft >= cb)
583 return VINF_SUCCESS;
584 cb -= cbLeft;
585 uRva += cbLeft;
586
587#if 0 /* This shouldn't ever be necessary. */
588 if ( i + 1 < pImage->cRegions
589 && uRva < pImage->aRegions[i + 1].uRva)
590 {
591 cbLeft = pImage->aRegions[i + 1].uRva - uRva;
592 if (cbLeft >= cb)
593 return VINF_SUCCESS;
594 cb -= cbLeft;
595 uRva += cbLeft;
596 }
597#endif
598 }
599 }
600
601 return supHardNtVpSetInfo2(pThis, cbOrg == cb ? VERR_SUP_VP_SECTION_NOT_MAPPED : VERR_SUP_VP_SECTION_NOT_FULLY_MAPPED,
602 IMAGE_LOG_NAME_FMT ": RVA range %#x-%#x is not mapped?", IMAGE_LOG_NAME(pImage), uRva, uRva + cb - 1);
603}
604
605#ifndef VBOX_WITH_MINIMAL_HARDENING
606
607DECLINLINE(bool) supHardNtVpIsModuleNameMatch(PSUPHNTVPIMAGE pImage, const char *pszModule)
608{
609 if (pImage->fDll)
610 {
611 const char *pszImageNm = pImage->pszName;
612 for (;;)
613 {
614 char chLeft = *pszImageNm++;
615 char chRight = *pszModule++;
616 if (chLeft != chRight)
617 {
618 Assert(chLeft == RT_C_TO_LOWER(chLeft));
619 if (chLeft != RT_C_TO_LOWER(chRight))
620 {
621 if ( chRight == '\0'
622 && chLeft == '.'
623 && pszImageNm[0] == 'd'
624 && pszImageNm[1] == 'l'
625 && pszImageNm[2] == 'l'
626 && pszImageNm[3] == '\0')
627 return true;
628 break;
629 }
630 }
631
632 if (chLeft == '\0')
633 return true;
634 }
635 }
636
637 return false;
638}
639
640
641/**
642 * Worker for supHardNtVpGetImport that looks up a module in the module table.
643 *
644 * @returns Pointer to the module if found, NULL if not found.
645 * @param pThis The process validator instance.
646 * @param pszModule The name of the module we're looking for.
647 */
648static PSUPHNTVPIMAGE supHardNtVpFindModule(PSUPHNTVPSTATE pThis, const char *pszModule)
649{
650 /*
651 * Check out the hint first.
652 */
653 if ( pThis->iImageHint < pThis->cImages
654 && supHardNtVpIsModuleNameMatch(&pThis->aImages[pThis->iImageHint], pszModule))
655 return &pThis->aImages[pThis->iImageHint];
656
657 /*
658 * Linear array search next.
659 */
660 uint32_t i = pThis->cImages;
661 while (i-- > 0)
662 if (supHardNtVpIsModuleNameMatch(&pThis->aImages[i], pszModule))
663 {
664 pThis->iImageHint = i;
665 return &pThis->aImages[i];
666 }
667
668 /* No cigar. */
669 return NULL;
670}
671
672#endif /* !VBOX_WITH_MINIMAL_HARDENING */
673
674
675/**
676 * @callback_method_impl{FNRTLDRIMPORT}
677 */
678static DECLCALLBACK(int) supHardNtVpGetImport(RTLDRMOD hLdrMod, const char *pszModule, const char *pszSymbol, unsigned uSymbol,
679 PRTLDRADDR pValue, void *pvUser)
680{
681 /*SUP_DPRINTF(("supHardNtVpGetImport: %s / %#x / %s.\n", pszModule, uSymbol, pszSymbol));*/
682 PSUPHNTVPSTATE const pThis = (PSUPHNTVPSTATE)pvUser;
683
684#ifndef VBOX_WITH_MINIMAL_HARDENING
685 RT_NOREF1(hLdrMod);
686
687 int rc = VERR_MODULE_NOT_FOUND;
688 PSUPHNTVPIMAGE pImage = supHardNtVpFindModule(pThis, pszModule);
689 if (pImage)
690 {
691 rc = RTLdrGetSymbolEx(pImage->pCacheEntry->hLdrMod, pImage->pCacheEntry->pbBits,
692 pImage->uImageBase, uSymbol, pszSymbol, pValue);
693 if (RT_SUCCESS(rc))
694 return rc;
695 }
696 /*
697 * API set hacks.
698 */
699 else if (!RTStrNICmp(pszModule, RT_STR_TUPLE("api-ms-win-")))
700 {
701 static const char * const s_apszDlls[] = { "ntdll.dll", "kernelbase.dll", "kernel32.dll" };
702 for (uint32_t i = 0; i < RT_ELEMENTS(s_apszDlls); i++)
703 {
704 pImage = supHardNtVpFindModule(pThis, s_apszDlls[i]);
705 if (pImage)
706 {
707 rc = RTLdrGetSymbolEx(pImage->pCacheEntry->hLdrMod, pImage->pCacheEntry->pbBits,
708 pImage->uImageBase, uSymbol, pszSymbol, pValue);
709 if (RT_SUCCESS(rc))
710 return rc;
711 if (rc != VERR_SYMBOL_NOT_FOUND)
712 break;
713 }
714 }
715 }
716
717 /*
718 * Deal with forwarders.
719 * ASSUMES no forwarders thru any api-ms-win-core-*.dll.
720 * ASSUMES forwarders are resolved after one redirection.
721 */
722 if (rc == VERR_LDR_FORWARDER)
723 {
724 size_t cbInfo = RT_MIN((uint32_t)*pValue, sizeof(RTLDRIMPORTINFO) + 32);
725 PRTLDRIMPORTINFO pInfo = (PRTLDRIMPORTINFO)alloca(cbInfo);
726 rc = RTLdrQueryForwarderInfo(pImage->pCacheEntry->hLdrMod, pImage->pCacheEntry->pbBits,
727 uSymbol, pszSymbol, pInfo, cbInfo);
728 if (RT_SUCCESS(rc))
729 {
730 rc = VERR_MODULE_NOT_FOUND;
731 pImage = supHardNtVpFindModule(pThis, pInfo->szModule);
732 if (pImage)
733 {
734 rc = RTLdrGetSymbolEx(pImage->pCacheEntry->hLdrMod, pImage->pCacheEntry->pbBits,
735 pImage->uImageBase, pInfo->iOrdinal, pInfo->pszSymbol, pValue);
736 if (RT_SUCCESS(rc))
737 return rc;
738
739 SUP_DPRINTF(("supHardNtVpGetImport: Failed to find symbol '%s' in '%s' (forwarded from %s / %s): %Rrc\n",
740 pInfo->pszSymbol, pInfo->szModule, pszModule, pszSymbol, rc));
741 if (rc == VERR_LDR_FORWARDER)
742 rc = VERR_LDR_FORWARDER_CHAIN_TOO_LONG;
743 }
744 else
745 SUP_DPRINTF(("supHardNtVpGetImport: Failed to find forwarder module '%s' (%#x / %s; originally %s / %#x / %s): %Rrc\n",
746 pInfo->szModule, pInfo->iOrdinal, pInfo->pszSymbol, pszModule, uSymbol, pszSymbol, rc));
747 }
748 else
749 SUP_DPRINTF(("supHardNtVpGetImport: RTLdrQueryForwarderInfo failed on symbol %#x/'%s' in '%s': %Rrc\n",
750 uSymbol, pszSymbol, pszModule, rc));
751 }
752 else
753 SUP_DPRINTF(("supHardNtVpGetImport: Failed to find symbol %#x / '%s' in '%s': %Rrc\n",
754 uSymbol, pszSymbol, pszModule, rc));
755 return rc;
756
757#else /* VBOX_WITH_MINIMAL_HARDENING */
758 /*
759 * We don't care about correct imports here, as we will skip the import
760 * table while comparing image bits. (If we wanted to produce correct
761 * imports, we'd have to track all the DLLs in the process, which would be
762 * bothersome and expensive while not really gaining any better certainty
763 * that we are actually executing the image we're looking at.)
764 */
765 RT_NOREF(hLdrMod, pszModule, pszSymbol, uSymbol);
766 *pValue = pThis->aImages[0].uImageBase - PAGE_SIZE;
767 return VINF_SUCCESS;
768#endif /* VBOX_WITH_MINIMAL_HARDENING */
769}
770
771
772/**
773 * Compares process memory with the disk content.
774 *
775 * @returns VBox status code.
776 * @param pThis The process scanning state structure (for the
777 * two scratch buffers).
778 * @param pImage The image data collected during the address
779 * space scan.
780 */
781static int supHardNtVpVerifyImageMemoryCompare(PSUPHNTVPSTATE pThis, PSUPHNTVPIMAGE pImage)
782{
783
784 /*
785 * Read and find the file headers.
786 */
787 int rc = supHardNtVpReadImage(pImage, 0 /*off*/, pThis->abFile, sizeof(pThis->abFile));
788 if (RT_FAILURE(rc))
789 return supHardNtVpSetInfo2(pThis, VERR_SUP_VP_IMAGE_HDR_READ_ERROR,
790 IMAGE_LOG_NAME_FMT ": Error reading image header: %Rrc", IMAGE_LOG_NAME(pImage), rc);
791
792 uint32_t offNtHdrs = 0;
793 PIMAGE_DOS_HEADER pDosHdr = (PIMAGE_DOS_HEADER)&pThis->abFile[0];
794 if (pDosHdr->e_magic == IMAGE_DOS_SIGNATURE)
795 {
796 offNtHdrs = pDosHdr->e_lfanew;
797 if (offNtHdrs > 512 || offNtHdrs < sizeof(*pDosHdr))
798 return supHardNtVpSetInfo2(pThis, VERR_SUP_VP_BAD_MZ_OFFSET,
799 IMAGE_LOG_NAME_FMT ": Unexpected e_lfanew value: %#x", IMAGE_LOG_NAME(pImage), offNtHdrs);
800 }
801 PIMAGE_NT_HEADERS pNtHdrs = (PIMAGE_NT_HEADERS)&pThis->abFile[offNtHdrs];
802 PIMAGE_NT_HEADERS32 pNtHdrs32 = (PIMAGE_NT_HEADERS32)pNtHdrs;
803 if (pNtHdrs->Signature != IMAGE_NT_SIGNATURE)
804 return supHardNtVpSetInfo2(pThis, VERR_SUP_VP_BAD_IMAGE_SIGNATURE,
805 IMAGE_LOG_NAME_FMT ": No PE signature at %#x: %#x", IMAGE_LOG_NAME(pImage), offNtHdrs, pNtHdrs->Signature);
806
807 /*
808 * Do basic header validation.
809 */
810#ifdef RT_ARCH_AMD64
811 const uint16_t uExpectedMachine = IMAGE_FILE_MACHINE_AMD64;
812#elif defined(RT_ARCH_ARM64)
813 const uint16_t uExpectedMachine = IMAGE_FILE_MACHINE_ARM64;
814#elif defined(RT_ARCH_X86)
815 const uint16_t uExpectedMachine = IMAGE_FILE_MACHINE_I386;
816#else
817# error "port me"
818#endif
819#if ARCH_BITS == 64
820 if (pNtHdrs->FileHeader.Machine != uExpectedMachine && !pImage->f32bitResourceDll)
821#elif defined
822 if (pNtHdrs->FileHeader.Machine != uExpectedMachine)
823#endif
824 return supHardNtVpSetInfo2(pThis, VERR_SUP_VP_UNEXPECTED_IMAGE_MACHINE,
825 IMAGE_LOG_NAME_FMT ": Unexpected machine: %#x (expected %#x)",
826 IMAGE_LOG_NAME(pImage), pNtHdrs->FileHeader.Machine, uExpectedMachine);
827 bool const fIs32Bit = pNtHdrs->FileHeader.Machine == IMAGE_FILE_MACHINE_I386;
828
829 if (pNtHdrs->FileHeader.SizeOfOptionalHeader != (fIs32Bit ? sizeof(IMAGE_OPTIONAL_HEADER32) : sizeof(IMAGE_OPTIONAL_HEADER64)))
830 return supHardNtVpSetInfo2(pThis, VERR_SUP_VP_BAD_OPTIONAL_HEADER,
831 IMAGE_LOG_NAME_FMT ": Unexpected optional header size: %#x",
832 IMAGE_LOG_NAME(pImage), pNtHdrs->FileHeader.SizeOfOptionalHeader);
833
834 if (pNtHdrs->OptionalHeader.Magic != (fIs32Bit ? IMAGE_NT_OPTIONAL_HDR32_MAGIC : IMAGE_NT_OPTIONAL_HDR64_MAGIC))
835 return supHardNtVpSetInfo2(pThis, VERR_SUP_VP_BAD_OPTIONAL_HEADER,
836 IMAGE_LOG_NAME_FMT ": Unexpected optional header magic: %#x", IMAGE_LOG_NAME(pImage), pNtHdrs->OptionalHeader.Magic);
837
838 uint32_t cDirs = (fIs32Bit ? pNtHdrs32->OptionalHeader.NumberOfRvaAndSizes : pNtHdrs->OptionalHeader.NumberOfRvaAndSizes);
839 if (cDirs != IMAGE_NUMBEROF_DIRECTORY_ENTRIES)
840 return supHardNtVpSetInfo2(pThis, VERR_SUP_VP_BAD_OPTIONAL_HEADER,
841 IMAGE_LOG_NAME_FMT ": Unexpected data dirs: %#x", IMAGE_LOG_NAME(pImage), cDirs);
842
843 /*
844 * Before we start comparing things, store what we need to know from the headers.
845 */
846 uint32_t const cSections = pNtHdrs->FileHeader.NumberOfSections;
847 if (cSections > RT_ELEMENTS(pThis->aSecHdrs))
848 return supHardNtVpSetInfo2(pThis, VERR_SUP_VP_TOO_MANY_SECTIONS,
849 IMAGE_LOG_NAME_FMT ": Too many section headers: %#x", IMAGE_LOG_NAME(pImage), cSections);
850 suplibHardenedMemCopy(pThis->aSecHdrs, (fIs32Bit ? (void *)(pNtHdrs32 + 1) : (void *)(pNtHdrs + 1)),
851 cSections * sizeof(IMAGE_SECTION_HEADER));
852
853 uintptr_t const uImageBase = fIs32Bit ? pNtHdrs32->OptionalHeader.ImageBase : pNtHdrs->OptionalHeader.ImageBase;
854 if (uImageBase & PAGE_OFFSET_MASK)
855 return supHardNtVpSetInfo2(pThis, VERR_SUP_VP_BAD_IMAGE_BASE,
856 IMAGE_LOG_NAME_FMT ": Invalid image base: %p", IMAGE_LOG_NAME(pImage), uImageBase);
857
858 uint32_t const cbImage = fIs32Bit ? pNtHdrs32->OptionalHeader.SizeOfImage : pNtHdrs->OptionalHeader.SizeOfImage;
859 uint32_t const cbImagePgAligned = RT_ALIGN_32(cbImage, PAGE_SIZE);
860 if (RT_ALIGN_32(pImage->cbImage, PAGE_SIZE) != cbImagePgAligned && !pImage->fApiSetSchemaOnlySection1)
861 {
862 /*
863 * Since 24h2, the kernel loader appends three pages to system images (from
864 * the looks of it). This manifests in an additional 2 (or 1) mapping regions,
865 * one which is execute/read (one page in 27718) and a 2nd one which is reserved.
866 * They both assocated with the DLL section object, so not like the typical AV
867 * allocations placed right after DLLs for entertaining unsolicited code changes.
868 */
869 if (pImage->cbImage < cbImagePgAligned)
870 return supHardNtVpSetInfo2(pThis, VERR_SUP_VP_BAD_IMAGE_SIZE,
871 IMAGE_LOG_NAME_FMT ": SizeOfImage (%#x) is larger than the mapping size (%#x)",
872 IMAGE_LOG_NAME(pImage), cbImage, pImage->cbImage);
873
874 /* This code has to be paranoid, so we must put some kind of limit on this extra
875 space. 64KB is taken out of thin (late night office) air, as per usual. */
876 if (pImage->cbImage > cbImagePgAligned + _64K)
877 return supHardNtVpSetInfo2(pThis, VERR_SUP_VP_BAD_IMAGE_SIZE,
878 IMAGE_LOG_NAME_FMT ": SizeOfImage (%#x) isn't close enough to the mapping size (%#x) - diff %#x bytes; max expected is 64KB",
879 IMAGE_LOG_NAME(pImage), cbImage, pImage->cbImage, pImage->cbImage - cbImage);
880
881 /* Locate the mapping region for the extra pages: */
882 if (pImage->cRegions <= 1)
883 return supHardNtVpSetInfo2(pThis, VERR_SUP_VP_BAD_IMAGE_SIZE,
884 IMAGE_LOG_NAME_FMT ": SizeOfImage (%#x) is smaller than the mapping size (%#x) and there are less than two mapping regions!",
885 IMAGE_LOG_NAME(pImage), cbImage, pImage->cbImage);
886 uint32_t iRegion = pImage->cRegions - 1;
887 while (iRegion > 0 && pImage->aRegions[iRegion].uRva > cbImagePgAligned)
888 iRegion--;
889 if (pImage->aRegions[iRegion].uRva != cbImagePgAligned)
890 return supHardNtVpSetInfo2(pThis, VERR_SUP_VP_BAD_IMAGE_SIZE,
891 IMAGE_LOG_NAME_FMT ": SizeOfImage (%#x) is smaller than the mapping size (%#x) and we cannot locate the region(s) for the extra space! (iRegion=%d: uRva=%#x, expected %#x)",
892 IMAGE_LOG_NAME(pImage), cbImage, pImage->cbImage, iRegion, pImage->aRegions[iRegion].uRva,
893 cbImagePgAligned);
894
895 /* Check that none of the pages are both writable & executable. */
896 for (uint32_t i = iRegion; i < pImage->cRegions; i++)
897 if (pImage->aRegions[i].fProt & PAGE_EXECUTE_READWRITE)
898 return supHardNtVpSetInfo2(pThis, VERR_SUP_VP_BAD_IMAGE_SIZE,
899 IMAGE_LOG_NAME_FMT ": SizeOfImage (%#x) is smaller than the mapping size (%#x) and extra page %#x LB %#x are RWX (%#x)!",
900 IMAGE_LOG_NAME(pImage), cbImage, pImage->cbImage,
901 pImage->aRegions[i].uRva, pImage->aRegions[i].cb, pImage->aRegions[i].fProt);
902 /** @todo more restrictions on this? */
903 }
904
905 if (cbImage != RTLdrSize(pImage->pCacheEntry->hLdrMod))
906 return supHardNtVpSetInfo2(pThis, VERR_SUP_VP_BAD_IMAGE_SIZE,
907 IMAGE_LOG_NAME_FMT ": SizeOfImage (%#x) differs from what RTLdrSize returns (%#zx)",
908 IMAGE_LOG_NAME(pImage), cbImage, RTLdrSize(pImage->pCacheEntry->hLdrMod));
909
910 uint32_t const cbSectAlign = fIs32Bit ? pNtHdrs32->OptionalHeader.SectionAlignment : pNtHdrs->OptionalHeader.SectionAlignment;
911 if ( !RT_IS_POWER_OF_TWO(cbSectAlign)
912 || cbSectAlign < PAGE_SIZE
913 || cbSectAlign > (pImage->fApiSetSchemaOnlySection1 ? _64K : (uint32_t)PAGE_SIZE) )
914 return supHardNtVpSetInfo2(pThis, VERR_SUP_VP_BAD_SECTION_ALIGNMENT_VALUE,
915 IMAGE_LOG_NAME_FMT ": Unexpected SectionAlignment value: %#x", IMAGE_LOG_NAME(pImage), cbSectAlign);
916
917 uint32_t const cbFileAlign = fIs32Bit ? pNtHdrs32->OptionalHeader.FileAlignment : pNtHdrs->OptionalHeader.FileAlignment;
918 if (!RT_IS_POWER_OF_TWO(cbFileAlign) || cbFileAlign < 512 || cbFileAlign > PAGE_SIZE || cbFileAlign > cbSectAlign)
919 return supHardNtVpSetInfo2(pThis, VERR_SUP_VP_BAD_FILE_ALIGNMENT_VALUE,
920 IMAGE_LOG_NAME_FMT ": Unexpected FileAlignment value: %#x (cbSectAlign=%#x)",
921 IMAGE_LOG_NAME(pImage), cbFileAlign, cbSectAlign);
922
923 uint32_t const cbHeaders = fIs32Bit ? pNtHdrs32->OptionalHeader.SizeOfHeaders : pNtHdrs->OptionalHeader.SizeOfHeaders;
924 uint32_t const cbMinHdrs = offNtHdrs + (fIs32Bit ? sizeof(*pNtHdrs32) : sizeof(*pNtHdrs) )
925 + sizeof(IMAGE_SECTION_HEADER) * cSections;
926 if (cbHeaders < cbMinHdrs)
927 return supHardNtVpSetInfo2(pThis, VERR_SUP_VP_BAD_SIZE_OF_HEADERS,
928 IMAGE_LOG_NAME_FMT ": Headers are too small: %#x < %#x (cSections=%#x)",
929 IMAGE_LOG_NAME(pImage), cbHeaders, cbMinHdrs, cSections);
930 uint32_t const cbHdrsFile = RT_ALIGN_32(cbHeaders, cbFileAlign);
931 if (cbHdrsFile > sizeof(pThis->abFile))
932 return supHardNtVpSetInfo2(pThis, VERR_SUP_VP_BAD_SIZE_OF_HEADERS,
933 IMAGE_LOG_NAME_FMT ": Headers are larger than expected: %#x/%#x (expected max %zx)",
934 IMAGE_LOG_NAME(pImage), cbHeaders, cbHdrsFile, sizeof(pThis->abFile));
935
936 /*
937 * Save some header fields we might be using later on.
938 */
939 pImage->fImageCharecteristics = pNtHdrs->FileHeader.Characteristics;
940 pImage->fDllCharecteristics = fIs32Bit ? pNtHdrs32->OptionalHeader.DllCharacteristics : pNtHdrs->OptionalHeader.DllCharacteristics;
941
942 /*
943 * Correct the apisetschema image base, size and region rva.
944 */
945 if (pImage->fApiSetSchemaOnlySection1)
946 {
947 pImage->uImageBase -= pThis->aSecHdrs[0].VirtualAddress;
948 pImage->cbImage += pThis->aSecHdrs[0].VirtualAddress;
949 pImage->aRegions[0].uRva = pThis->aSecHdrs[0].VirtualAddress;
950 }
951
952 /*
953 * Get relocated bits.
954 */
955 uint8_t *pbBits;
956 if (pThis->enmKind == SUPHARDNTVPKIND_CHILD_PURIFICATION)
957 rc = supHardNtLdrCacheEntryGetBits(pImage->pCacheEntry, &pbBits, pImage->uImageBase, NULL /*pfnGetImport*/, pThis,
958 pThis->pErrInfo);
959 else
960 rc = supHardNtLdrCacheEntryGetBits(pImage->pCacheEntry, &pbBits, pImage->uImageBase, supHardNtVpGetImport, pThis,
961 pThis->pErrInfo);
962 if (RT_FAILURE(rc))
963 return rc;
964
965 /* XP SP3 does not set ImageBase to load address. It fixes up the image on load time though. */
966 if (g_uNtVerCombined >= SUP_NT_VER_VISTA)
967 {
968 if (fIs32Bit)
969 ((PIMAGE_NT_HEADERS32)&pbBits[offNtHdrs])->OptionalHeader.ImageBase = (uint32_t)pImage->uImageBase;
970 else
971 ((PIMAGE_NT_HEADERS)&pbBits[offNtHdrs])->OptionalHeader.ImageBase = pImage->uImageBase;
972 }
973
974 /*
975 * Figure out areas we should skip during comparison.
976 */
977 uint32_t cSkipAreas = 0;
978 SUPHNTVPSKIPAREA aSkipAreas[7];
979#ifndef VBOX_WITH_MINIMAL_HARDENING
980 if (pImage->fNtCreateSectionPatch)
981 {
982 RTLDRADDR uValue;
983 if (pThis->enmKind == SUPHARDNTVPKIND_VERIFY_ONLY)
984 {
985 /* Ignore our NtCreateSection hack. */
986 rc = RTLdrGetSymbolEx(pImage->pCacheEntry->hLdrMod, pbBits, 0, UINT32_MAX, "NtCreateSection", &uValue);
987 if (RT_FAILURE(rc))
988 return supHardNtVpSetInfo2(pThis, rc, "%s: Failed to find 'NtCreateSection': %Rrc", pImage->pszName, rc);
989 aSkipAreas[cSkipAreas].uRva = (uint32_t)uValue;
990 aSkipAreas[cSkipAreas++].cb = ARCH_BITS == 32 ? 5 : 12;
991
992 /* Ignore our LdrLoadDll hack. */
993 rc = RTLdrGetSymbolEx(pImage->pCacheEntry->hLdrMod, pbBits, 0, UINT32_MAX, "LdrLoadDll", &uValue);
994 if (RT_FAILURE(rc))
995 return supHardNtVpSetInfo2(pThis, rc, "%s: Failed to find 'LdrLoadDll': %Rrc", pImage->pszName, rc);
996 aSkipAreas[cSkipAreas].uRva = (uint32_t)uValue;
997 aSkipAreas[cSkipAreas++].cb = ARCH_BITS == 32 ? 5 : 12;
998 }
999
1000 /* Ignore our patched LdrInitializeThunk hack. */
1001 rc = RTLdrGetSymbolEx(pImage->pCacheEntry->hLdrMod, pbBits, 0, UINT32_MAX, "LdrInitializeThunk", &uValue);
1002 if (RT_FAILURE(rc))
1003 return supHardNtVpSetInfo2(pThis, rc, "%s: Failed to find 'LdrInitializeThunk': %Rrc", pImage->pszName, rc);
1004 aSkipAreas[cSkipAreas].uRva = (uint32_t)uValue;
1005 aSkipAreas[cSkipAreas++].cb = 14;
1006
1007 /* Ignore our patched KiUserApcDispatcher hack. */
1008 rc = RTLdrGetSymbolEx(pImage->pCacheEntry->hLdrMod, pbBits, 0, UINT32_MAX, "KiUserApcDispatcher", &uValue);
1009 if (RT_FAILURE(rc))
1010 return supHardNtVpSetInfo2(pThis, rc, "%s: Failed to find 'KiUserApcDispatcher': %Rrc", pImage->pszName, rc);
1011 aSkipAreas[cSkipAreas].uRva = (uint32_t)uValue;
1012 aSkipAreas[cSkipAreas++].cb = 14;
1013
1014# ifndef VBOX_WITHOUT_HARDENDED_XCPT_LOGGING
1015 /* Ignore our patched KiUserExceptionDispatcher hack. */
1016 rc = RTLdrGetSymbolEx(pImage->pCacheEntry->hLdrMod, pbBits, 0, UINT32_MAX, "KiUserExceptionDispatcher", &uValue);
1017 if (RT_FAILURE(rc))
1018 return supHardNtVpSetInfo2(pThis, rc, "%s: Failed to find 'KiUserExceptionDispatcher': %Rrc", pImage->pszName, rc);
1019 aSkipAreas[cSkipAreas].uRva = (uint32_t)uValue + (HC_ARCH_BITS == 64);
1020 aSkipAreas[cSkipAreas++].cb = HC_ARCH_BITS == 64 ? 13 : 12;
1021# endif
1022
1023 /* LdrSystemDllInitBlock is filled in by the kernel. It mainly contains addresses of 32-bit ntdll method for wow64. */
1024 rc = RTLdrGetSymbolEx(pImage->pCacheEntry->hLdrMod, pbBits, 0, UINT32_MAX, "LdrSystemDllInitBlock", &uValue);
1025 if (RT_SUCCESS(rc))
1026 {
1027 aSkipAreas[cSkipAreas].uRva = (uint32_t)uValue;
1028 aSkipAreas[cSkipAreas++].cb = RT_MAX(pbBits[(uint32_t)uValue], 0x50);
1029 }
1030
1031 Assert(cSkipAreas <= RT_ELEMENTS(aSkipAreas));
1032 }
1033
1034#else /* VBOX_WITH_MINIMAL_HARDENING */
1035# if 0 /* This is .rdata stuff, which it turns out contains more random stuff modified via the loadcfg. So, skipping it .rdata. */
1036 /* Skip the IAT as we don't process fixups correctly. */
1037 uint32_t const offAfterHdrs = offNtHdrs
1038 + (!fIs32Bit ? pNtHdrs->OptionalHeader.SizeOfHeaders : pNtHdrs32->OptionalHeader.SizeOfHeaders);
1039 aSkipAreas[cSkipAreas].uRva = !fIs32Bit
1040 ? pNtHdrs->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IAT].VirtualAddress
1041 : pNtHdrs32->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IAT].VirtualAddress;
1042 aSkipAreas[cSkipAreas].cb = !fIs32Bit
1043 ? pNtHdrs->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IAT].Size
1044 : pNtHdrs32->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IAT].Size;
1045 if ( aSkipAreas[cSkipAreas].cb > 0
1046 && aSkipAreas[cSkipAreas].cb < pImage->cbImage / 4
1047 && aSkipAreas[cSkipAreas].uRva > offAfterHdrs
1048 && aSkipAreas[cSkipAreas].uRva < pImage->cbImage
1049 && aSkipAreas[cSkipAreas].uRva + aSkipAreas[cSkipAreas].cb <= pImage->cbImage)
1050 cSkipAreas++;
1051# endif
1052#endif /* VBOX_WITH_MINIMAL_HARDENING */
1053
1054 /*
1055 * Compare the file header with the loaded bits. The loader will fiddle
1056 * with image base, changing it to the actual load address.
1057 */
1058 if (!pImage->fApiSetSchemaOnlySection1)
1059 {
1060 rc = supHardNtVpFileMemCompareSection(pThis, pImage, 0 /*uRva*/, cbHdrsFile, pbBits, -1, NULL, 0, PAGE_READONLY);
1061 if (RT_FAILURE(rc))
1062 return rc;
1063
1064 rc = supHardNtVpCheckSectionProtection(pThis, pImage, 0 /*uRva*/, cbHdrsFile, PAGE_READONLY);
1065 if (RT_FAILURE(rc))
1066 return rc;
1067 }
1068
1069 /*
1070 * Validate sections:
1071 * - Check them against the mapping regions.
1072 * - Check section bits according to enmKind.
1073 */
1074 uint32_t fPrevProt = PAGE_READONLY;
1075 uint32_t uRva = cbHdrsFile;
1076 for (uint32_t i = 0; i < cSections; i++)
1077 {
1078 /* Validate the section. */
1079 uint32_t uSectRva = pThis->aSecHdrs[i].VirtualAddress;
1080 if (uSectRva < uRva || uSectRva > cbImage || RT_ALIGN_32(uSectRva, cbSectAlign) != uSectRva)
1081 return supHardNtVpSetInfo2(pThis, VERR_SUP_VP_BAD_SECTION_RVA,
1082 IMAGE_LOG_NAME_FMT ": Section %u: Invalid virtual address: %#x (uRva=%#x, cbImage=%#x, cbSectAlign=%#x)",
1083 IMAGE_LOG_NAME(pImage), i, uSectRva, uRva, cbImage, cbSectAlign);
1084 uint32_t cbMap = pThis->aSecHdrs[i].Misc.VirtualSize;
1085 if (cbMap > cbImage || uRva + cbMap > cbImage)
1086 return supHardNtVpSetInfo2(pThis, VERR_SUP_VP_BAD_SECTION_VIRTUAL_SIZE,
1087 IMAGE_LOG_NAME_FMT ": Section %u: Invalid virtual size: %#x (uSectRva=%#x, uRva=%#x, cbImage=%#x)",
1088 IMAGE_LOG_NAME(pImage), i, cbMap, uSectRva, uRva, cbImage);
1089 uint32_t cbFile = pThis->aSecHdrs[i].SizeOfRawData;
1090 if (cbFile != RT_ALIGN_32(cbFile, cbFileAlign) || cbFile > RT_ALIGN_32(cbMap, cbSectAlign))
1091 return supHardNtVpSetInfo2(pThis, VERR_SUP_VP_BAD_SECTION_FILE_SIZE,
1092 IMAGE_LOG_NAME_FMT ": Section %u: Invalid file size: %#x (cbMap=%#x, uSectRva=%#x)",
1093 IMAGE_LOG_NAME(pImage), i, cbFile, cbMap, uSectRva);
1094
1095 /* Validate the protection and bits. */
1096 if (!pImage->fApiSetSchemaOnlySection1 || i == 0)
1097 {
1098 uint32_t fProt;
1099 switch (pThis->aSecHdrs[i].Characteristics & (IMAGE_SCN_MEM_EXECUTE | IMAGE_SCN_MEM_READ | IMAGE_SCN_MEM_WRITE))
1100 {
1101 case IMAGE_SCN_MEM_READ:
1102 fProt = PAGE_READONLY;
1103 break;
1104 case IMAGE_SCN_MEM_READ | IMAGE_SCN_MEM_WRITE:
1105 fProt = PAGE_READWRITE;
1106 if ( pThis->enmKind != SUPHARDNTVPKIND_VERIFY_ONLY
1107 && pThis->enmKind != SUPHARDNTVPKIND_CHILD_PURIFICATION
1108 && !suplibHardenedMemComp(pThis->aSecHdrs[i].Name, ".mrdata", 8)) /* w8.1, ntdll. Changed by proc init. */
1109 fProt = PAGE_READONLY;
1110 break;
1111 case IMAGE_SCN_MEM_READ | IMAGE_SCN_MEM_EXECUTE:
1112 fProt = PAGE_EXECUTE_READ;
1113 break;
1114 case IMAGE_SCN_MEM_EXECUTE:
1115 fProt = PAGE_EXECUTE;
1116 break;
1117 case IMAGE_SCN_MEM_EXECUTE | IMAGE_SCN_MEM_READ | IMAGE_SCN_MEM_WRITE:
1118 /* Only the executable is allowed to have this section,
1119 and it's protected after we're done patching. */
1120 if (!pImage->fDll)
1121 {
1122 if (pThis->enmKind == SUPHARDNTVPKIND_CHILD_PURIFICATION)
1123 fProt = PAGE_EXECUTE_READWRITE;
1124 else
1125 fProt = PAGE_EXECUTE_READ;
1126 break;
1127 }
1128 default:
1129 return supHardNtVpSetInfo2(pThis, VERR_SUP_VP_UNEXPECTED_SECTION_FLAGS,
1130 IMAGE_LOG_NAME_FMT ": Section %u: Unexpected characteristics: %#x (uSectRva=%#x, cbMap=%#x)",
1131 IMAGE_LOG_NAME(pImage), i, pThis->aSecHdrs[i].Characteristics, uSectRva, cbMap);
1132 }
1133
1134 /* The section bits. Child purification verifies all, normal
1135 verification verifies all except where the executable is
1136 concerned (due to opening vboxdrv during early process init). */
1137 if ( ( (pThis->aSecHdrs[i].Characteristics & (IMAGE_SCN_MEM_EXECUTE | IMAGE_SCN_CNT_CODE))
1138 && !(pThis->aSecHdrs[i].Characteristics & IMAGE_SCN_MEM_WRITE))
1139#ifndef VBOX_WITH_MINIMAL_HARDENING /* only executable segments */
1140 || (pThis->aSecHdrs[i].Characteristics & (IMAGE_SCN_MEM_READ | IMAGE_SCN_MEM_WRITE)) == IMAGE_SCN_MEM_READ
1141 || (pThis->enmKind == SUPHARDNTVPKIND_VERIFY_ONLY && pImage->fDll)
1142 || pThis->enmKind == SUPHARDNTVPKIND_CHILD_PURIFICATION
1143#endif
1144 )
1145 {
1146 rc = VINF_SUCCESS;
1147 if (uRva < uSectRva && !pImage->fApiSetSchemaOnlySection1) /* Any gap worth checking? */
1148 rc = supHardNtVpFileMemCompareSection(pThis, pImage, uRva, uSectRva - uRva, pbBits + uRva,
1149 i - 1, NULL, 0, fPrevProt);
1150 if (RT_SUCCESS(rc))
1151 rc = supHardNtVpFileMemCompareSection(pThis, pImage, uSectRva, cbMap, pbBits + uSectRva,
1152 i, aSkipAreas, cSkipAreas, fProt);
1153 if (RT_SUCCESS(rc))
1154 {
1155 uint32_t cbMapAligned = i + 1 < cSections && !pImage->fApiSetSchemaOnlySection1
1156 ? RT_ALIGN_32(cbMap, cbSectAlign) : RT_ALIGN_32(cbMap, PAGE_SIZE);
1157 if (cbMapAligned > cbMap)
1158 rc = supHardNtVpFileMemCompareSection(pThis, pImage, uSectRva + cbMap, cbMapAligned - cbMap,
1159 g_abRTZeroPage, i, NULL, 0, fProt);
1160 }
1161 if (RT_FAILURE(rc))
1162 return rc;
1163 }
1164
1165 /* The protection (must be checked afterwards!). */
1166 rc = supHardNtVpCheckSectionProtection(pThis, pImage, uSectRva, RT_ALIGN_32(cbMap, PAGE_SIZE), fProt);
1167 if (RT_FAILURE(rc))
1168 return rc;
1169
1170 fPrevProt = fProt;
1171 }
1172
1173 /* Advance the RVA. */
1174 uRva = uSectRva + RT_ALIGN_32(cbMap, cbSectAlign);
1175 }
1176
1177 return VINF_SUCCESS;
1178}
1179
1180
1181/**
1182 * Verifies the signature of the given image on disk, then checks if the memory
1183 * mapping matches what we verified.
1184 *
1185 * @returns VBox status code.
1186 * @param pThis The process scanning state structure (for the
1187 * two scratch buffers).
1188 * @param pImage The image data collected during the address
1189 * space scan.
1190 */
1191static int supHardNtVpVerifyImage(PSUPHNTVPSTATE pThis, PSUPHNTVPIMAGE pImage)
1192{
1193 /*
1194 * Validate the file signature first, then do the memory compare.
1195 */
1196 int rc;
1197 if ( pImage->pCacheEntry != NULL
1198 && pImage->pCacheEntry->hLdrMod != NIL_RTLDRMOD)
1199 {
1200 rc = supHardNtLdrCacheEntryVerify(pImage->pCacheEntry, pImage->Name.UniStr.Buffer, pThis->pErrInfo);
1201 if (RT_SUCCESS(rc))
1202 rc = supHardNtVpVerifyImageMemoryCompare(pThis, pImage);
1203 }
1204 else
1205 rc = supHardNtVpSetInfo2(pThis, VERR_OPEN_FAILED, "pCacheEntry/hLdrMod is NIL! Impossible!");
1206 return rc;
1207}
1208
1209
1210/**
1211 * Verifies that there is only one thread in the process.
1212 *
1213 * @returns VBox status code.
1214 * @param hProcess The process.
1215 * @param hThread The thread.
1216 * @param pErrInfo Pointer to error info structure. Optional.
1217 */
1218DECLHIDDEN(int) supHardNtVpThread(HANDLE hProcess, HANDLE hThread, PRTERRINFO pErrInfo)
1219{
1220 RT_NOREF1(hProcess);
1221
1222 /*
1223 * Use the ThreadAmILastThread request to check that there is only one
1224 * thread in the process.
1225 * Seems this isn't entirely reliable when hThread isn't the current thread?
1226 */
1227 ULONG cbIgn = 0;
1228 ULONG fAmI = 0;
1229 NTSTATUS rcNt = NtQueryInformationThread(hThread, ThreadAmILastThread, &fAmI, sizeof(fAmI), &cbIgn);
1230 if (!NT_SUCCESS(rcNt))
1231 return supHardNtVpSetInfo1(pErrInfo, VERR_SUP_VP_NT_QI_THREAD_ERROR,
1232 "NtQueryInformationThread/ThreadAmILastThread -> %#x", rcNt);
1233 if (!fAmI)
1234 return supHardNtVpSetInfo1(pErrInfo, VERR_SUP_VP_THREAD_NOT_ALONE,
1235 "More than one thread in process");
1236
1237 /** @todo Would be nice to verify the relationship between hProcess and hThread
1238 * as well... */
1239 return VINF_SUCCESS;
1240}
1241
1242
1243/**
1244 * Verifies that there isn't a debugger attached to the process.
1245 *
1246 * @returns VBox status code.
1247 * @param hProcess The process.
1248 * @param pErrInfo Pointer to error info structure. Optional.
1249 */
1250DECLHIDDEN(int) supHardNtVpDebugger(HANDLE hProcess, PRTERRINFO pErrInfo)
1251{
1252#ifndef VBOX_WITHOUT_DEBUGGER_CHECKS
1253 /*
1254 * Use the ProcessDebugPort request to check there is no debugger
1255 * currently attached to the process.
1256 */
1257 ULONG cbIgn = 0;
1258 uintptr_t uPtr = ~(uintptr_t)0;
1259 NTSTATUS rcNt = NtQueryInformationProcess(hProcess,
1260 ProcessDebugPort,
1261 &uPtr, sizeof(uPtr), &cbIgn);
1262 if (!NT_SUCCESS(rcNt))
1263 return supHardNtVpSetInfo1(pErrInfo, VERR_SUP_VP_NT_QI_PROCESS_DBG_PORT_ERROR,
1264 "NtQueryInformationProcess/ProcessDebugPort -> %#x", rcNt);
1265 if (uPtr != 0)
1266 return supHardNtVpSetInfo1(pErrInfo, VERR_SUP_VP_DEBUGGED,
1267 "Debugger attached (%#zx)", uPtr);
1268#else
1269 RT_NOREF2(hProcess, pErrInfo);
1270#endif /* !VBOX_WITHOUT_DEBUGGER_CHECKS */
1271 return VINF_SUCCESS;
1272}
1273
1274
1275/**
1276 * Matches two UNICODE_STRING structures in a case sensitive fashion.
1277 *
1278 * @returns true if equal, false if not.
1279 * @param pUniStr1 The first unicode string.
1280 * @param pUniStr2 The first unicode string.
1281 */
1282static bool supHardNtVpAreUniStringsEqual(PCUNICODE_STRING pUniStr1, PCUNICODE_STRING pUniStr2)
1283{
1284 if (pUniStr1->Length != pUniStr2->Length)
1285 return false;
1286 return suplibHardenedMemComp(pUniStr1->Buffer, pUniStr2->Buffer, pUniStr1->Length) == 0;
1287}
1288
1289
1290/**
1291 * Performs a case insensitive comparison of an ASCII and an UTF-16 file name.
1292 *
1293 * @returns true / false
1294 * @param pszName1 The ASCII name.
1295 * @param pwszName2 The UTF-16 name.
1296 */
1297static bool supHardNtVpAreNamesEqual(const char *pszName1, PCRTUTF16 pwszName2)
1298{
1299 for (;;)
1300 {
1301 char ch1 = *pszName1++;
1302 RTUTF16 wc2 = *pwszName2++;
1303 if (ch1 != wc2)
1304 {
1305 ch1 = RT_C_TO_LOWER(ch1);
1306 wc2 = wc2 < 0x80 ? RT_C_TO_LOWER(wc2) : wc2;
1307 if (ch1 != wc2)
1308 return false;
1309 }
1310 if (!ch1)
1311 return true;
1312 }
1313}
1314
1315
1316/**
1317 * Compares two paths, expanding 8.3 short names as needed.
1318 *
1319 * @returns true / false.
1320 * @param pUniStr1 The first path. Must be zero terminated!
1321 * @param pUniStr2 The second path. Must be zero terminated!
1322 */
1323static bool supHardNtVpArePathsEqual(PCUNICODE_STRING pUniStr1, PCUNICODE_STRING pUniStr2)
1324{
1325 /* Both strings must be null terminated. */
1326 Assert(pUniStr1->Buffer[pUniStr1->Length / sizeof(WCHAR)] == '\0');
1327 Assert(pUniStr2->Buffer[pUniStr1->Length / sizeof(WCHAR)] == '\0');
1328
1329 /* Simple compare first.*/
1330 if (supHardNtVpAreUniStringsEqual(pUniStr1, pUniStr2))
1331 return true;
1332
1333 /* Make long names if needed. */
1334 UNICODE_STRING UniStrLong1 = { 0, 0, NULL };
1335 if (RTNtPathFindPossible8dot3Name(pUniStr1->Buffer))
1336 {
1337 int rc = RTNtPathExpand8dot3PathA(pUniStr1, false /*fPathOnly*/, &UniStrLong1);
1338 if (RT_SUCCESS(rc))
1339 pUniStr1 = &UniStrLong1;
1340 }
1341
1342 UNICODE_STRING UniStrLong2 = { 0, 0, NULL };
1343 if (RTNtPathFindPossible8dot3Name(pUniStr2->Buffer))
1344 {
1345 int rc = RTNtPathExpand8dot3PathA(pUniStr2, false /*fPathOnly*/, &UniStrLong2);
1346 if (RT_SUCCESS(rc))
1347 pUniStr2 = &UniStrLong2;
1348 }
1349
1350 /* Compare again. */
1351 bool fCompare = supHardNtVpAreUniStringsEqual(pUniStr1, pUniStr2);
1352
1353 /* Clean up. */
1354 if (UniStrLong1.Buffer)
1355 RTUtf16Free(UniStrLong1.Buffer);
1356 if (UniStrLong2.Buffer)
1357 RTUtf16Free(UniStrLong2.Buffer);
1358
1359 return fCompare;
1360}
1361
1362
1363/**
1364 * Records an additional memory region for an image.
1365 *
1366 * May trash pThis->abMemory.
1367 *
1368 * @returns VBox status code.
1369 * @retval VINF_OBJECT_DESTROYED if we've unmapped the image (child
1370 * purification only).
1371 * @param pThis The process scanning state structure.
1372 * @param pImage The new image structure. Only the unicode name
1373 * buffer is valid (it's zero-terminated).
1374 * @param pMemInfo The memory information for the image.
1375 */
1376static int supHardNtVpNewImage(PSUPHNTVPSTATE pThis, PSUPHNTVPIMAGE pImage, PMEMORY_BASIC_INFORMATION pMemInfo)
1377{
1378 /*
1379 * If the filename or path contains short names, we have to get the long
1380 * path so that we will recognize the DLLs and their location.
1381 */
1382 int rc83Exp = VERR_IGNORED;
1383 PUNICODE_STRING pLongName = &pImage->Name.UniStr;
1384 if (RTNtPathFindPossible8dot3Name(pLongName->Buffer))
1385 {
1386 AssertCompile(sizeof(pThis->abMemory) > sizeof(pImage->Name));
1387 PUNICODE_STRING pTmp = (PUNICODE_STRING)pThis->abMemory;
1388 pTmp->MaximumLength = (USHORT)RT_MIN(_64K - 1, sizeof(pThis->abMemory) - sizeof(*pTmp)) - sizeof(RTUTF16);
1389 pTmp->Length = pImage->Name.UniStr.Length;
1390 pTmp->Buffer = (PRTUTF16)(pTmp + 1);
1391 memcpy(pTmp->Buffer, pLongName->Buffer, pLongName->Length + sizeof(RTUTF16));
1392
1393 rc83Exp = RTNtPathExpand8dot3Path(pTmp, false /*fPathOnly*/);
1394 Assert(rc83Exp == VINF_SUCCESS);
1395 Assert(pTmp->Buffer[pTmp->Length / sizeof(RTUTF16)] == '\0');
1396 if (rc83Exp == VINF_SUCCESS)
1397 SUP_DPRINTF(("supHardNtVpNewImage: 8dot3 -> long: '%ls' -> '%ls'\n", pLongName->Buffer, pTmp->Buffer));
1398 else
1399 SUP_DPRINTF(("supHardNtVpNewImage: RTNtPathExpand8dot3Path returns %Rrc for '%ls' (-> '%ls')\n",
1400 rc83Exp, pLongName->Buffer, pTmp->Buffer));
1401
1402 pLongName = pTmp;
1403 }
1404
1405 /*
1406 * Extract the final component.
1407 */
1408 RTUTF16 wc;
1409 unsigned cwcDirName = pLongName->Length / sizeof(WCHAR);
1410 PCRTUTF16 pwszFilename = &pLongName->Buffer[cwcDirName];
1411 while ( cwcDirName > 0
1412 && (wc = pwszFilename[-1]) != '\\'
1413 && wc != '/'
1414 && wc != ':')
1415 {
1416 pwszFilename--;
1417 cwcDirName--;
1418 }
1419 if (!*pwszFilename)
1420 return supHardNtVpSetInfo2(pThis, VERR_SUP_VP_NO_IMAGE_MAPPING_NAME,
1421 "Empty filename (len=%u) for image at %p.", pLongName->Length, pMemInfo->BaseAddress);
1422
1423 /*
1424 * Drop trailing slashes from the directory name.
1425 */
1426 while ( cwcDirName > 0
1427 && ( pLongName->Buffer[cwcDirName - 1] == '\\'
1428 || pLongName->Buffer[cwcDirName - 1] == '/'))
1429 cwcDirName--;
1430
1431#ifndef VBOX_WITH_MINIMAL_HARDENING
1432 /*
1433 * Match it against known DLLs.
1434 */
1435 pImage->pszName = NULL;
1436 for (uint32_t i = 0; i < RT_ELEMENTS(g_apszSupNtVpAllowedDlls); i++)
1437 if (supHardNtVpAreNamesEqual(g_apszSupNtVpAllowedDlls[i], pwszFilename))
1438 {
1439 pImage->pszName = g_apszSupNtVpAllowedDlls[i];
1440 pImage->fDll = true;
1441
1442# ifndef VBOX_PERMIT_VISUAL_STUDIO_PROFILING
1443 /* The directory name must match the one we've got for System32. */
1444 if ( ( cwcDirName * sizeof(WCHAR) != g_System32NtPath.UniStr.Length
1445 || suplibHardenedMemComp(pLongName->Buffer, g_System32NtPath.UniStr.Buffer, cwcDirName * sizeof(WCHAR)) )
1446# ifdef VBOX_PERMIT_MORE
1447 && ( pImage->pszName[0] != 'a'
1448 || pImage->pszName[1] != 'c'
1449 || !supHardViIsAppPatchDir(pLongName->Buffer, pLongName->Length / sizeof(WCHAR)) )
1450# endif
1451 )
1452 return supHardNtVpSetInfo2(pThis, VERR_SUP_VP_NON_SYSTEM32_DLL,
1453 "Expected %ls to be loaded from %ls.",
1454 pLongName->Buffer, g_System32NtPath.UniStr.Buffer);
1455# ifdef VBOX_PERMIT_MORE
1456 if (g_uNtVerCombined < SUP_NT_VER_W70 && i >= VBOX_PERMIT_MORE_FIRST_IDX)
1457 pImage->pszName = NULL; /* hard limit: user32.dll is unwanted prior to w7. */
1458# endif
1459
1460# endif /* VBOX_PERMIT_VISUAL_STUDIO_PROFILING */
1461 break;
1462 }
1463 if (!pImage->pszName)
1464#endif /* !VBOX_WITH_MINIMAL_HARDENING */
1465 {
1466 /*
1467 * Not a known DLL, is it a known executable?
1468 */
1469#ifdef VBOX_WITH_MINIMAL_HARDENING
1470 pImage->fDll = true;
1471#endif
1472 for (uint32_t i = 0; i < RT_ELEMENTS(g_apszSupNtVpAllowedVmExes); i++)
1473 if (supHardNtVpAreNamesEqual(g_apszSupNtVpAllowedVmExes[i], pwszFilename))
1474 {
1475#ifndef VBOX_WITH_MINIMAL_HARDENING
1476 pImage->pszName = g_apszSupNtVpAllowedVmExes[i];
1477#endif
1478 pImage->fDll = false;
1479 break;
1480 }
1481 }
1482#ifndef VBOX_WITH_MINIMAL_HARDENING
1483 if (!pImage->pszName)
1484 {
1485 /*
1486 * Unknown image.
1487 *
1488 * If we're cleaning up a child process, we can unmap the offending
1489 * DLL... Might have interesting side effects, or at least interesting
1490 * as in "may you live in interesting times".
1491 */
1492# ifdef IN_RING3
1493 if ( pMemInfo->AllocationBase == pMemInfo->BaseAddress
1494 && pThis->enmKind == SUPHARDNTVPKIND_CHILD_PURIFICATION)
1495 {
1496 SUP_DPRINTF(("supHardNtVpScanVirtualMemory: Unmapping image mem at %p (%p LB %#zx) - '%ls'\n",
1497 pMemInfo->AllocationBase, pMemInfo->BaseAddress, pMemInfo->RegionSize, pwszFilename));
1498 NTSTATUS rcNt = NtUnmapViewOfSection(pThis->hProcess, pMemInfo->AllocationBase);
1499 if (NT_SUCCESS(rcNt))
1500 return VINF_OBJECT_DESTROYED;
1501 pThis->cFixes++;
1502 SUP_DPRINTF(("supHardNtVpScanVirtualMemory: NtUnmapViewOfSection(,%p) failed: %#x\n", pMemInfo->AllocationBase, rcNt));
1503 }
1504 else if (pThis->enmKind == SUPHARDNTVPKIND_SELF_PURIFICATION_LIMITED)
1505 {
1506 SUP_DPRINTF(("supHardNtVpScanVirtualMemory: Ignoring unknown mem at %p LB %#zx (base %p) - '%ls'\n",
1507 pMemInfo->BaseAddress, pMemInfo->RegionSize, pMemInfo->AllocationBase, pwszFilename));
1508 return VINF_OBJECT_DESTROYED;
1509 }
1510# endif
1511 /*
1512 * Special error message if we can.
1513 */
1514 if ( pMemInfo->AllocationBase == pMemInfo->BaseAddress
1515 && ( supHardNtVpAreNamesEqual("sysfer.dll", pwszFilename)
1516 || supHardNtVpAreNamesEqual("sysfer32.dll", pwszFilename)
1517 || supHardNtVpAreNamesEqual("sysfer64.dll", pwszFilename)
1518 || supHardNtVpAreNamesEqual("sysfrethunk.dll", pwszFilename)) )
1519 {
1520 supHardNtVpSetInfo2(pThis, VERR_SUP_VP_SYSFER_DLL,
1521 "Found %ls at %p - This is probably part of Symantec Endpoint Protection. \n"
1522 "You or your admin need to add and exception to the Application and Device Control (ADC) "
1523 "component (or disable it) to prevent ADC from injecting itself into the VirtualBox VM processes. "
1524 "See http://www.symantec.com/connect/articles/creating-application-control-exclusions-symantec-endpoint-protection-121"
1525 , pLongName->Buffer, pMemInfo->BaseAddress);
1526 return pThis->rcResult = VERR_SUP_VP_SYSFER_DLL; /* Try make sure this is what the user sees first! */
1527 }
1528 return supHardNtVpSetInfo2(pThis, VERR_SUP_VP_NOT_KNOWN_DLL_OR_EXE,
1529 "Unknown image file %ls at %p. (rc83Exp=%Rrc)",
1530 pLongName->Buffer, pMemInfo->BaseAddress, rc83Exp);
1531 }
1532
1533 /*
1534 * Checks for multiple mappings of the same DLL but with different image file paths.
1535 */
1536 uint32_t i = pThis->cImages;
1537 while (i-- > 1)
1538 if (pImage->pszName == pThis->aImages[i].pszName)
1539 return supHardNtVpSetInfo2(pThis, VERR_SUP_VP_DUPLICATE_DLL_MAPPING,
1540 "Duplicate image entries for %s: %ls and %ls",
1541 pImage->pszName, pImage->Name.UniStr.Buffer, pThis->aImages[i].Name.UniStr.Buffer);
1542#endif /* !VBOX_WITH_MINIMAL_HARDENING */
1543
1544 /*
1545 * Since it's a new image, we expect to be at the start of the mapping now.
1546 */
1547 if (pMemInfo->AllocationBase != pMemInfo->BaseAddress)
1548 return supHardNtVpSetInfo2(pThis, VERR_SUP_VP_IMAGE_MAPPING_BASE_ERROR,
1549 "Invalid AllocationBase/BaseAddress for " IMAGE_LOG_NAME_FMT ": %p vs %p.",
1550 IMAGE_LOG_NAME(pImage), pMemInfo->AllocationBase, pMemInfo->BaseAddress);
1551
1552 /*
1553 * Check for size/rva overflow.
1554 */
1555 if (pMemInfo->RegionSize >= _2G)
1556 return supHardNtVpSetInfo2(pThis, VERR_SUP_VP_TOO_LARGE_REGION,
1557 "Region 0 of image " IMAGE_LOG_NAME_FMT " is too large: %p.",
1558 IMAGE_LOG_NAME(pImage), pMemInfo->RegionSize);
1559
1560 /*
1561 * Fill in details from the memory info.
1562 */
1563 pImage->uImageBase = (uintptr_t)pMemInfo->AllocationBase;
1564 pImage->cbImage = pMemInfo->RegionSize;
1565 pImage->pCacheEntry= NULL;
1566 pImage->cRegions = 1;
1567 pImage->aRegions[0].uRva = 0;
1568 pImage->aRegions[0].cb = (uint32_t)pMemInfo->RegionSize;
1569 pImage->aRegions[0].fProt = pMemInfo->Protect;
1570
1571#ifndef VBOX_WITH_MINIMAL_HARDENING
1572 if (suplibHardenedStrCmp(pImage->pszName, "ntdll.dll") == 0)
1573 pImage->fNtCreateSectionPatch = true;
1574 else if (suplibHardenedStrCmp(pImage->pszName, "apisetschema.dll") == 0)
1575 pImage->fApiSetSchemaOnlySection1 = true; /** @todo Check the ApiSetMap field in the PEB. */
1576# ifdef VBOX_PERMIT_MORE
1577 else if (suplibHardenedStrCmp(pImage->pszName, "acres.dll") == 0)
1578 pImage->f32bitResourceDll = true;
1579# endif
1580#endif /* !VBOX_WITH_MINIMAL_HARDENING */
1581
1582 return VINF_SUCCESS;
1583}
1584
1585
1586/**
1587 * Records an additional memory region for an image.
1588 *
1589 * @returns VBox status code.
1590 * @param pThis The process scanning state structure.
1591 * @param pImage The image.
1592 * @param pMemInfo The memory information for the region.
1593 */
1594static int supHardNtVpAddRegion(PSUPHNTVPSTATE pThis, PSUPHNTVPIMAGE pImage, PMEMORY_BASIC_INFORMATION pMemInfo)
1595{
1596 /*
1597 * Make sure the base address matches.
1598 */
1599 if (pImage->uImageBase != (uintptr_t)pMemInfo->AllocationBase)
1600 return supHardNtVpSetInfo2(pThis, VERR_SUPLIB_NT_PROCESS_UNTRUSTED_3,
1601 "Base address mismatch for " IMAGE_LOG_NAME_FMT ": have %p, found %p for region %p LB %#zx.",
1602 IMAGE_LOG_NAME(pImage), pImage->uImageBase, pMemInfo->AllocationBase,
1603 pMemInfo->BaseAddress, pMemInfo->RegionSize);
1604
1605 /*
1606 * Check for size and rva overflows.
1607 */
1608 uintptr_t uRva = (uintptr_t)pMemInfo->BaseAddress - pImage->uImageBase;
1609 if (pMemInfo->RegionSize >= _2G)
1610 return supHardNtVpSetInfo2(pThis, VERR_SUP_VP_TOO_LARGE_REGION,
1611 "Region %u of image " IMAGE_LOG_NAME_FMT " is too large: %p/%p.",
1612 IMAGE_LOG_NAME(pImage), pMemInfo->RegionSize, uRva);
1613 if (uRva >= _2G)
1614 return supHardNtVpSetInfo2(pThis, VERR_SUP_VP_TOO_HIGH_REGION_RVA,
1615 "Region %u of image " IMAGE_LOG_NAME_FMT " is too high: %p/%p.",
1616 IMAGE_LOG_NAME(pImage), pMemInfo->RegionSize, uRva);
1617
1618
1619 /*
1620 * Record the region.
1621 */
1622 uint32_t iRegion = pImage->cRegions;
1623 if (iRegion + 1 >= RT_ELEMENTS(pImage->aRegions))
1624 return supHardNtVpSetInfo2(pThis, VERR_SUP_VP_TOO_MANY_IMAGE_REGIONS,
1625 "Too many regions for " IMAGE_LOG_NAME_FMT ".", IMAGE_LOG_NAME(pImage));
1626 pImage->aRegions[iRegion].uRva = (uint32_t)uRva;
1627 pImage->aRegions[iRegion].cb = (uint32_t)pMemInfo->RegionSize;
1628 pImage->aRegions[iRegion].fProt = pMemInfo->Protect;
1629 pImage->cbImage = pImage->aRegions[iRegion].uRva + pImage->aRegions[iRegion].cb;
1630 pImage->cRegions++;
1631 pImage->fApiSetSchemaOnlySection1 = false;
1632
1633 return VINF_SUCCESS;
1634}
1635
1636
1637#ifdef IN_RING3
1638/**
1639 * Frees (or replaces) executable memory of allocation type private.
1640 *
1641 * @returns True if nothing really bad happen, false if to quit ASAP because we
1642 * killed the process being scanned.
1643 * @param pThis The process scanning state structure. Details
1644 * about images are added to this.
1645 * @param hProcess The process to verify.
1646 * @param pMemInfo The information we've got on this private
1647 * executable memory.
1648 */
1649static bool supHardNtVpFreeOrReplacePrivateExecMemory(PSUPHNTVPSTATE pThis, HANDLE hProcess,
1650 MEMORY_BASIC_INFORMATION const *pMemInfo)
1651{
1652 NTSTATUS rcNt;
1653
1654 /*
1655 * Try figure the entire allocation size. Free/Alloc may fail otherwise.
1656 */
1657 PVOID pvFree = pMemInfo->AllocationBase;
1658 SIZE_T cbFree = pMemInfo->RegionSize + ((uintptr_t)pMemInfo->BaseAddress - (uintptr_t)pMemInfo->AllocationBase);
1659 for (;;)
1660 {
1661 SIZE_T cbActual = 0;
1662 MEMORY_BASIC_INFORMATION MemInfo2 = { 0, 0, 0, 0, 0, 0, 0 };
1663 uintptr_t uPtrNext = (uintptr_t)pvFree + cbFree;
1664 rcNt = g_pfnNtQueryVirtualMemory(hProcess,
1665 (void const *)uPtrNext,
1666 MemoryBasicInformation,
1667 &MemInfo2,
1668 sizeof(MemInfo2),
1669 &cbActual);
1670 if (!NT_SUCCESS(rcNt))
1671 break;
1672 if (pMemInfo->AllocationBase != MemInfo2.AllocationBase)
1673 break;
1674 if (MemInfo2.RegionSize == 0)
1675 break;
1676 cbFree += MemInfo2.RegionSize;
1677 }
1678 SUP_DPRINTF(("supHardNtVpFreeOrReplacePrivateExecMemory: %s exec mem at %p (LB %#zx, %p LB %#zx)\n",
1679 pThis->fFlags & SUPHARDNTVP_F_EXEC_ALLOC_REPLACE_WITH_RW ? "Replacing" : "Freeing",
1680 pvFree, cbFree, pMemInfo->BaseAddress, pMemInfo->RegionSize));
1681
1682 /*
1683 * In the BSOD workaround mode, we need to make a copy of the memory before
1684 * freeing it. Bird abuses this code for logging purposes too.
1685 */
1686 uintptr_t uCopySrc = (uintptr_t)pvFree;
1687 size_t cbCopy = 0;
1688 void *pvCopy = NULL;
1689 //if (pThis->fFlags & SUPHARDNTVP_F_EXEC_ALLOC_REPLACE_WITH_RW)
1690 {
1691 cbCopy = cbFree;
1692 pvCopy = RTMemAllocZ(cbCopy);
1693 if (!pvCopy)
1694 {
1695 supHardNtVpSetInfo2(pThis, VERR_SUP_VP_REPLACE_VIRTUAL_MEMORY_FAILED, "RTMemAllocZ(%#zx) failed", cbCopy);
1696 return true;
1697 }
1698
1699 rcNt = supHardNtVpReadMem(hProcess, uCopySrc, pvCopy, cbCopy);
1700 if (!NT_SUCCESS(rcNt))
1701 supHardNtVpSetInfo2(pThis, VERR_SUP_VP_REPLACE_VIRTUAL_MEMORY_FAILED,
1702 "Error reading data from original alloc: %#x (%p LB %#zx)", rcNt, uCopySrc, cbCopy, rcNt);
1703 for (size_t off = 0; off < cbCopy; off += 256)
1704 {
1705 size_t const cbChunk = RT_MIN(256, cbCopy - off);
1706 void const *pvChunk = (uint8_t const *)pvCopy + off;
1707 if (!ASMMemIsZero(pvChunk, cbChunk))
1708 SUP_DPRINTF(("%.*RhxD\n", cbChunk, pvChunk));
1709 }
1710 if (pThis->fFlags & SUPHARDNTVP_F_EXEC_ALLOC_REPLACE_WITH_RW)
1711 supR3HardenedLogFlush();
1712 }
1713
1714 /*
1715 * Free the memory.
1716 */
1717 for (uint32_t i = 0; i < 10; i++)
1718 {
1719 PVOID pvFreeInOut = pvFree;
1720 SIZE_T cbFreeInOut = 0;
1721 rcNt = NtFreeVirtualMemory(hProcess, &pvFreeInOut, &cbFreeInOut, MEM_RELEASE);
1722 if (NT_SUCCESS(rcNt))
1723 {
1724 SUP_DPRINTF(("supHardNtVpFreeOrReplacePrivateExecMemory: Free attempt #1 succeeded: %#x [%p/%p LB 0/%#zx]\n",
1725 rcNt, pvFree, pvFreeInOut, cbFreeInOut));
1726 supR3HardenedLogFlush();
1727 }
1728 else
1729 {
1730 SUP_DPRINTF(("supHardNtVpFreeOrReplacePrivateExecMemory: Free attempt #1 failed: %#x [%p LB 0]\n", rcNt, pvFree));
1731 supR3HardenedLogFlush();
1732 pvFreeInOut = pvFree;
1733 cbFreeInOut = cbFree;
1734 rcNt = NtFreeVirtualMemory(hProcess, &pvFreeInOut, &cbFreeInOut, MEM_RELEASE);
1735 if (NT_SUCCESS(rcNt))
1736 {
1737 SUP_DPRINTF(("supHardNtVpFreeOrReplacePrivateExecMemory: Free attempt #2 succeeded: %#x [%p/%p LB %#zx/%#zx]\n",
1738 rcNt, pvFree, pvFreeInOut, cbFree, cbFreeInOut));
1739 supR3HardenedLogFlush();
1740 }
1741 else
1742 {
1743 SUP_DPRINTF(("supHardNtVpFreeOrReplacePrivateExecMemory: Free attempt #2 failed: %#x [%p LB %#zx]\n",
1744 rcNt, pvFree, cbFree));
1745 supR3HardenedLogFlush();
1746 pvFreeInOut = pMemInfo->BaseAddress;
1747 cbFreeInOut = pMemInfo->RegionSize;
1748 rcNt = NtFreeVirtualMemory(hProcess, &pvFreeInOut, &cbFreeInOut, MEM_RELEASE);
1749 if (NT_SUCCESS(rcNt))
1750 {
1751 pvFree = pMemInfo->BaseAddress;
1752 cbFree = pMemInfo->RegionSize;
1753 SUP_DPRINTF(("supHardNtVpFreeOrReplacePrivateExecMemory: Free attempt #3 succeeded [%p LB %#zx]\n",
1754 pvFree, cbFree));
1755 supR3HardenedLogFlush();
1756 }
1757 else
1758 supHardNtVpSetInfo2(pThis, VERR_SUP_VP_FREE_VIRTUAL_MEMORY_FAILED,
1759 "NtFreeVirtualMemory [%p LB %#zx and %p LB %#zx] failed: %#x",
1760 pvFree, cbFree, pMemInfo->BaseAddress, pMemInfo->RegionSize, rcNt);
1761 }
1762 }
1763
1764 /*
1765 * Query the region again, redo the free operation if there's still memory there.
1766 */
1767 if (!NT_SUCCESS(rcNt))
1768 break;
1769 SIZE_T cbActual = 0;
1770 MEMORY_BASIC_INFORMATION MemInfo3 = { 0, 0, 0, 0, 0, 0, 0 };
1771 NTSTATUS rcNt2 = g_pfnNtQueryVirtualMemory(hProcess, pvFree, MemoryBasicInformation,
1772 &MemInfo3, sizeof(MemInfo3), &cbActual);
1773 if (!NT_SUCCESS(rcNt2))
1774 break;
1775 SUP_DPRINTF(("supHardNtVpFreeOrReplacePrivateExecMemory: QVM after free %u: [%p]/%p LB %#zx s=%#x ap=%#x rp=%#p\n",
1776 i, MemInfo3.AllocationBase, MemInfo3.BaseAddress, MemInfo3.RegionSize, MemInfo3.State,
1777 MemInfo3.AllocationProtect, MemInfo3.Protect));
1778 supR3HardenedLogFlush();
1779 if (MemInfo3.State == MEM_FREE || !(pThis->fFlags & SUPHARDNTVP_F_EXEC_ALLOC_REPLACE_WITH_RW))
1780 break;
1781 NtYieldExecution();
1782 SUP_DPRINTF(("supHardNtVpFreeOrReplacePrivateExecMemory: Retrying free...\n"));
1783 supR3HardenedLogFlush();
1784 }
1785
1786 /*
1787 * Restore memory as non-executable - Kludge for Trend Micro sakfile.sys
1788 * and Digital Guardian dgmaster.sys BSODs.
1789 */
1790 if (NT_SUCCESS(rcNt) && (pThis->fFlags & SUPHARDNTVP_F_EXEC_ALLOC_REPLACE_WITH_RW))
1791 {
1792 PVOID pvAlloc = pvFree;
1793 SIZE_T cbAlloc = cbFree;
1794 rcNt = NtAllocateVirtualMemory(hProcess, &pvAlloc, 0, &cbAlloc, MEM_COMMIT, PAGE_READWRITE);
1795 if (!NT_SUCCESS(rcNt))
1796 {
1797 supHardNtVpSetInfo2(pThis, VERR_SUP_VP_REPLACE_VIRTUAL_MEMORY_FAILED,
1798 "NtAllocateVirtualMemory (%p LB %#zx) failed with rcNt=%#x allocating "
1799 "replacement memory for working around buggy protection software. "
1800 "See VBoxStartup.log for more details",
1801 pvAlloc, cbFree, rcNt);
1802 supR3HardenedLogFlush();
1803 NtTerminateProcess(hProcess, VERR_SUP_VP_REPLACE_VIRTUAL_MEMORY_FAILED);
1804 return false;
1805 }
1806
1807 if ( (uintptr_t)pvFree < (uintptr_t)pvAlloc
1808 || (uintptr_t)pvFree + cbFree > (uintptr_t)pvAlloc + cbFree)
1809 {
1810 supHardNtVpSetInfo2(pThis, VERR_SUP_VP_REPLACE_VIRTUAL_MEMORY_FAILED,
1811 "We wanted NtAllocateVirtualMemory to get us %p LB %#zx, but it returned %p LB %#zx.",
1812 pMemInfo->BaseAddress, pMemInfo->RegionSize, pvFree, cbFree, rcNt);
1813 supR3HardenedLogFlush();
1814 NtTerminateProcess(hProcess, VERR_SUP_VP_REPLACE_VIRTUAL_MEMORY_FAILED);
1815 return false;
1816 }
1817
1818 /*
1819 * Copy what we can, considering the 2nd free attempt.
1820 */
1821 uint8_t *pbDst = (uint8_t *)pvFree;
1822 size_t cbDst = cbFree;
1823 uint8_t *pbSrc = (uint8_t *)pvCopy;
1824 size_t cbSrc = cbCopy;
1825 if ((uintptr_t)pbDst != uCopySrc)
1826 {
1827 if ((uintptr_t)pbDst > uCopySrc)
1828 {
1829 uintptr_t cbAdj = (uintptr_t)pbDst - uCopySrc;
1830 pbSrc += cbAdj;
1831 cbSrc -= cbAdj;
1832 }
1833 else
1834 {
1835 uintptr_t cbAdj = uCopySrc - (uintptr_t)pbDst;
1836 pbDst += cbAdj;
1837 cbDst -= cbAdj;
1838 }
1839 }
1840 if (cbSrc > cbDst)
1841 cbSrc = cbDst;
1842
1843 SIZE_T cbWritten;
1844 rcNt = NtWriteVirtualMemory(hProcess, pbDst, pbSrc, cbSrc, &cbWritten);
1845 if (NT_SUCCESS(rcNt))
1846 {
1847 SUP_DPRINTF(("supHardNtVpFreeOrReplacePrivateExecMemory: Restored the exec memory as non-exec.\n"));
1848 supR3HardenedLogFlush();
1849 }
1850 else
1851 {
1852 supHardNtVpSetInfo2(pThis, VERR_SUP_VP_FREE_VIRTUAL_MEMORY_FAILED,
1853 "NtWriteVirtualMemory (%p LB %#zx) failed: %#x",
1854 pMemInfo->BaseAddress, pMemInfo->RegionSize, rcNt);
1855 supR3HardenedLogFlush();
1856 NtTerminateProcess(hProcess, VERR_SUP_VP_REPLACE_VIRTUAL_MEMORY_FAILED);
1857 return false;
1858 }
1859 }
1860 if (pvCopy)
1861 RTMemFree(pvCopy);
1862 return true;
1863}
1864#endif /* IN_RING3 */
1865
1866
1867/**
1868 * Scans the virtual memory of the process.
1869 *
1870 * This collects the locations of DLLs and the EXE, and verifies that executable
1871 * memory is only associated with these. May trash pThis->abMemory.
1872 *
1873 * @returns VBox status code.
1874 * @param pThis The process scanning state structure. Details
1875 * about images are added to this.
1876 * @param hProcess The process to verify.
1877 */
1878static int supHardNtVpScanVirtualMemory(PSUPHNTVPSTATE pThis, HANDLE hProcess)
1879{
1880 SUP_DPRINTF(("supHardNtVpScanVirtualMemory: enmKind=%s\n",
1881 pThis->enmKind == SUPHARDNTVPKIND_VERIFY_ONLY ? "VERIFY_ONLY" :
1882 pThis->enmKind == SUPHARDNTVPKIND_CHILD_PURIFICATION ? "CHILD_PURIFICATION" : "SELF_PURIFICATION"));
1883
1884 uint32_t cXpExceptions = 0;
1885 uintptr_t cbAdvance = 0;
1886 uintptr_t uPtrWhere = 0;
1887#if defined(VBOX_PERMIT_VERIFIER_DLL) || defined(VBOX_WITH_MINIMAL_HARDENING)
1888 for (uint32_t i = 0; i < 10240; i++)
1889#else
1890 for (uint32_t i = 0; i < 1024; i++)
1891#endif
1892 {
1893 SIZE_T cbActual = 0;
1894 MEMORY_BASIC_INFORMATION MemInfo = { 0, 0, 0, 0, 0, 0, 0 };
1895 NTSTATUS rcNt = g_pfnNtQueryVirtualMemory(hProcess,
1896 (void const *)uPtrWhere,
1897 MemoryBasicInformation,
1898 &MemInfo,
1899 sizeof(MemInfo),
1900 &cbActual);
1901 if (!NT_SUCCESS(rcNt))
1902 {
1903 if (rcNt == STATUS_INVALID_PARAMETER)
1904 return pThis->rcResult;
1905 return supHardNtVpSetInfo2(pThis, VERR_SUP_VP_NT_QI_VIRTUAL_MEMORY_ERROR,
1906 "NtQueryVirtualMemory failed for %p: %#x", uPtrWhere, rcNt);
1907 }
1908
1909 /*
1910 * Record images.
1911 */
1912 if ( MemInfo.Type == SEC_IMAGE
1913 || MemInfo.Type == SEC_PROTECTED_IMAGE
1914 || MemInfo.Type == (SEC_IMAGE | SEC_PROTECTED_IMAGE))
1915 {
1916 uint32_t iImg = pThis->cImages;
1917 rcNt = g_pfnNtQueryVirtualMemory(hProcess,
1918 (void const *)uPtrWhere,
1919 MemorySectionName,
1920 &pThis->aImages[iImg].Name,
1921 sizeof(pThis->aImages[iImg].Name) - sizeof(WCHAR),
1922 &cbActual);
1923 if (!NT_SUCCESS(rcNt))
1924 return supHardNtVpSetInfo2(pThis, VERR_SUP_VP_NT_QI_VIRTUAL_MEMORY_NM_ERROR,
1925 "NtQueryVirtualMemory/MemorySectionName failed for %p: %#x", uPtrWhere, rcNt);
1926 pThis->aImages[iImg].Name.UniStr.Buffer[pThis->aImages[iImg].Name.UniStr.Length / sizeof(WCHAR)] = '\0';
1927 SUP_DPRINTF((MemInfo.AllocationBase == MemInfo.BaseAddress
1928 ? " *%p-%p %#06x/%#06x %#09x %ls\n"
1929 : " %p-%p %#06x/%#06x %#09x %ls\n",
1930 MemInfo.BaseAddress, (uintptr_t)MemInfo.BaseAddress + MemInfo.RegionSize - 1, MemInfo.Protect,
1931 MemInfo.AllocationProtect, MemInfo.Type, pThis->aImages[iImg].Name.UniStr.Buffer));
1932
1933 /* New or existing image? */
1934 bool fNew = true;
1935 uint32_t iSearch = iImg;
1936 while (iSearch-- > 0)
1937 if (supHardNtVpAreUniStringsEqual(&pThis->aImages[iSearch].Name.UniStr, &pThis->aImages[iImg].Name.UniStr))
1938 {
1939 int rc = supHardNtVpAddRegion(pThis, &pThis->aImages[iSearch], &MemInfo);
1940 if (RT_FAILURE(rc))
1941 return rc;
1942 fNew = false;
1943 break;
1944 }
1945 else if (pThis->aImages[iSearch].uImageBase == (uintptr_t)MemInfo.AllocationBase)
1946 return supHardNtVpSetInfo2(pThis, VERR_SUP_VP_NT_MAPPING_NAME_CHANGED,
1947 "Unexpected base address match");
1948
1949 if (fNew)
1950 {
1951#ifdef VBOX_WITH_MINIMAL_HARDENING
1952 /* Prune non-executable images before trying to add another. We only want the executable. */
1953 if (iImg > 0 && pThis->aImages[iImg - 1].fDll)
1954 {
1955 pThis->cImages = --iImg;
1956 RT_ZERO(pThis->aImages[iImg]);
1957 pThis->aImages[iImg].Name = pThis->aImages[iImg + 1].Name;
1958 pThis->aImages[iImg].Name.UniStr.Buffer = pThis->aImages[iImg].Name.awcBuffer;
1959 }
1960#endif
1961 int rc = supHardNtVpNewImage(pThis, &pThis->aImages[iImg], &MemInfo);
1962 if (RT_SUCCESS(rc))
1963 {
1964 if (rc != VINF_OBJECT_DESTROYED)
1965 {
1966 pThis->cImages++;
1967 if (pThis->cImages >= RT_ELEMENTS(pThis->aImages))
1968 return supHardNtVpSetInfo2(pThis, VERR_SUP_VP_TOO_MANY_DLLS_LOADED,
1969 "Internal error: aImages is full.\n");
1970 }
1971 }
1972#ifdef IN_RING3 /* Continue and add more information if unknown DLLs are found. */
1973 else if (rc != VERR_SUP_VP_NOT_KNOWN_DLL_OR_EXE && rc != VERR_SUP_VP_NON_SYSTEM32_DLL)
1974 return rc;
1975#else
1976 else
1977 return rc;
1978#endif
1979 }
1980 }
1981 /*
1982 * XP, W2K3: Ignore the CSRSS read-only region as best we can.
1983 */
1984 else if ( (MemInfo.Protect & (PAGE_EXECUTE | PAGE_EXECUTE_READ | PAGE_EXECUTE_READWRITE | PAGE_EXECUTE_WRITECOPY))
1985 == PAGE_EXECUTE_READ
1986 && cXpExceptions == 0
1987 && (uintptr_t)MemInfo.BaseAddress >= UINT32_C(0x78000000)
1988 /* && MemInfo.BaseAddress == pPeb->ReadOnlySharedMemoryBase */
1989 && g_uNtVerCombined < SUP_MAKE_NT_VER_SIMPLE(6, 0) )
1990 {
1991 cXpExceptions++;
1992 SUP_DPRINTF((" %p-%p %#06x/%#06x %#09x XP CSRSS read-only region\n", MemInfo.BaseAddress,
1993 (uintptr_t)MemInfo.BaseAddress + MemInfo.RegionSize - 1, MemInfo.Protect,
1994 MemInfo.AllocationProtect, MemInfo.Type));
1995 }
1996 /*
1997 * Executable memory?
1998 */
1999#ifndef VBOX_PERMIT_VISUAL_STUDIO_PROFILING
2000 else if (MemInfo.Protect & (PAGE_EXECUTE | PAGE_EXECUTE_READ | PAGE_EXECUTE_READWRITE | PAGE_EXECUTE_WRITECOPY))
2001 {
2002 SUP_DPRINTF((MemInfo.AllocationBase == MemInfo.BaseAddress
2003 ? " *%p-%p %#06x/%#06x %#09x !!\n"
2004 : " %p-%p %#06x/%#06x %#09x !!\n",
2005 MemInfo.BaseAddress, (uintptr_t)MemInfo.BaseAddress + MemInfo.RegionSize - 1,
2006 MemInfo.Protect, MemInfo.AllocationProtect, MemInfo.Type));
2007# ifdef IN_RING3
2008 if (pThis->enmKind == SUPHARDNTVPKIND_CHILD_PURIFICATION)
2009 {
2010 /*
2011 * Free any private executable memory (sysplant.sys allocates executable memory).
2012 */
2013 if (MemInfo.Type == MEM_PRIVATE)
2014 {
2015 if (!supHardNtVpFreeOrReplacePrivateExecMemory(pThis, hProcess, &MemInfo))
2016 break;
2017 }
2018 /*
2019 * Unmap mapped memory, failing that, drop exec privileges.
2020 */
2021 else if (MemInfo.Type == MEM_MAPPED)
2022 {
2023 SUP_DPRINTF(("supHardNtVpScanVirtualMemory: Unmapping exec mem at %p (%p/%p LB %#zx)\n",
2024 uPtrWhere, MemInfo.AllocationBase, MemInfo.BaseAddress, MemInfo.RegionSize));
2025 rcNt = NtUnmapViewOfSection(hProcess, MemInfo.AllocationBase);
2026 if (!NT_SUCCESS(rcNt))
2027 {
2028 PVOID pvCopy = MemInfo.BaseAddress;
2029 SIZE_T cbCopy = MemInfo.RegionSize;
2030 NTSTATUS rcNt2 = NtProtectVirtualMemory(hProcess, &pvCopy, &cbCopy, PAGE_NOACCESS, NULL);
2031 if (!NT_SUCCESS(rcNt2))
2032 rcNt2 = NtProtectVirtualMemory(hProcess, &pvCopy, &cbCopy, PAGE_READONLY, NULL);
2033 if (!NT_SUCCESS(rcNt2))
2034 supHardNtVpSetInfo2(pThis, VERR_SUP_VP_UNMAP_AND_PROTECT_FAILED,
2035 "NtUnmapViewOfSection (%p/%p LB %#zx) failed: %#x (%#x)",
2036 MemInfo.AllocationBase, MemInfo.BaseAddress, MemInfo.RegionSize, rcNt, rcNt2);
2037 }
2038 }
2039 else
2040 supHardNtVpSetInfo2(pThis, VERR_SUP_VP_UNKOWN_MEM_TYPE,
2041 "Unknown executable memory type %#x at %p/%p LB %#zx",
2042 MemInfo.Type, MemInfo.AllocationBase, MemInfo.BaseAddress, MemInfo.RegionSize);
2043 pThis->cFixes++;
2044 }
2045 else if (pThis->enmKind != SUPHARDNTVPKIND_SELF_PURIFICATION_LIMITED)
2046# endif /* IN_RING3 */
2047 supHardNtVpSetInfo2(pThis, VERR_SUP_VP_FOUND_EXEC_MEMORY,
2048 "Found executable memory at %p (%p LB %#zx): type=%#x prot=%#x state=%#x aprot=%#x abase=%p",
2049 uPtrWhere, MemInfo.BaseAddress, MemInfo.RegionSize, MemInfo.Type, MemInfo.Protect,
2050 MemInfo.State, MemInfo.AllocationBase, MemInfo.AllocationProtect);
2051
2052# ifndef IN_RING3
2053 if (RT_FAILURE(pThis->rcResult))
2054 return pThis->rcResult;
2055# endif
2056 /* Continue add more information about the problematic process. */
2057 }
2058#endif /* VBOX_PERMIT_VISUAL_STUDIO_PROFILING */
2059 else
2060 SUP_DPRINTF((MemInfo.AllocationBase == MemInfo.BaseAddress
2061 ? " *%p-%p %#06x/%#06x %#09x\n"
2062 : " %p-%p %#06x/%#06x %#09x\n",
2063 MemInfo.BaseAddress, (uintptr_t)MemInfo.BaseAddress + MemInfo.RegionSize - 1,
2064 MemInfo.Protect, MemInfo.AllocationProtect, MemInfo.Type));
2065
2066 /*
2067 * Advance.
2068 */
2069 cbAdvance = MemInfo.RegionSize;
2070 if (uPtrWhere + cbAdvance <= uPtrWhere)
2071 return supHardNtVpSetInfo2(pThis, VERR_SUP_VP_EMPTY_REGION_TOO_LARGE,
2072 "Empty region at %p.", uPtrWhere);
2073 uPtrWhere += MemInfo.RegionSize;
2074 }
2075
2076 return supHardNtVpSetInfo2(pThis, VERR_SUP_VP_TOO_MANY_MEMORY_REGIONS,
2077 "Too many virtual memory regions.\n");
2078}
2079
2080
2081/**
2082 * Verifies the loader image, i.e. check cryptographic signatures if present.
2083 *
2084 * @returns VBox status code.
2085 * @param pEntry The loader cache entry.
2086 * @param pwszName The filename to use in error messages.
2087 * @param pErrInfo Where to return extened error information.
2088 */
2089DECLHIDDEN(int) supHardNtLdrCacheEntryVerify(PSUPHNTLDRCACHEENTRY pEntry, PCRTUTF16 pwszName, PRTERRINFO pErrInfo)
2090{
2091 int rc = VINF_SUCCESS;
2092 if (!pEntry->fVerified)
2093 {
2094 rc = supHardenedWinVerifyImageByLdrMod(pEntry->hLdrMod, pwszName, pEntry->pNtViRdr,
2095 false /*fAvoidWinVerifyTrust*/, NULL /*pfWinVerifyTrust*/, pErrInfo);
2096 pEntry->fVerified = RT_SUCCESS(rc);
2097 }
2098 return rc;
2099}
2100
2101
2102/**
2103 * Allocates a image bits buffer and calls RTLdrGetBits on them.
2104 *
2105 * An assumption here is that there won't ever be concurrent use of the cache.
2106 * It's currently 104% single threaded, non-reentrant. Thus, we can't reuse the
2107 * pbBits allocation.
2108 *
2109 * @returns VBox status code
2110 * @param pEntry The loader cache entry.
2111 * @param ppbBits Where to return the pointer to the allocation.
2112 * @param uBaseAddress The image base address, see RTLdrGetBits.
2113 * @param pfnGetImport Import getter, see RTLdrGetBits.
2114 * @param pvUser The user argument for @a pfnGetImport.
2115 * @param pErrInfo Where to return extened error information.
2116 */
2117DECLHIDDEN(int) supHardNtLdrCacheEntryGetBits(PSUPHNTLDRCACHEENTRY pEntry, uint8_t **ppbBits,
2118 RTLDRADDR uBaseAddress, PFNRTLDRIMPORT pfnGetImport, void *pvUser,
2119 PRTERRINFO pErrInfo)
2120{
2121 int rc;
2122
2123 /*
2124 * First time around we have to allocate memory before we can get the image bits.
2125 */
2126 if (!pEntry->pbBits)
2127 {
2128 size_t cbBits = RTLdrSize(pEntry->hLdrMod);
2129 if (cbBits >= _1M*32U)
2130 return supHardNtVpSetInfo1(pErrInfo, VERR_SUP_VP_IMAGE_TOO_BIG, "Image %s is too large: %zu bytes (%#zx).",
2131 pEntry->pszName, cbBits, cbBits);
2132
2133 pEntry->pbBits = (uint8_t *)RTMemAllocZ(cbBits);
2134 if (!pEntry->pbBits)
2135 return supHardNtVpSetInfo1(pErrInfo, VERR_SUP_VP_NO_MEMORY, "Failed to allocate %zu bytes for image %s.",
2136 cbBits, pEntry->pszName);
2137
2138 pEntry->fValidBits = false; /* paranoia */
2139
2140 rc = RTLdrGetBits(pEntry->hLdrMod, pEntry->pbBits, uBaseAddress, pfnGetImport, pvUser);
2141 if (RT_FAILURE(rc))
2142 return supHardNtVpSetInfo1(pErrInfo, VERR_SUP_VP_NO_MEMORY, "RTLdrGetBits failed on image %s: %Rrc",
2143 pEntry->pszName, rc);
2144 pEntry->uImageBase = uBaseAddress;
2145 pEntry->fValidBits = pfnGetImport == NULL;
2146
2147 }
2148 /*
2149 * Cache hit? No?
2150 *
2151 * Note! We cannot currently cache image bits for images with imports as we
2152 * don't control the way they're resolved. Fortunately, NTDLL and
2153 * the VM process images all have no imports.
2154 */
2155 else if ( !pEntry->fValidBits
2156 || pEntry->uImageBase != uBaseAddress
2157 || pfnGetImport)
2158 {
2159 pEntry->fValidBits = false;
2160
2161 rc = RTLdrGetBits(pEntry->hLdrMod, pEntry->pbBits, uBaseAddress, pfnGetImport, pvUser);
2162 if (RT_FAILURE(rc))
2163 return supHardNtVpSetInfo1(pErrInfo, VERR_SUP_VP_NO_MEMORY, "RTLdrGetBits failed on image %s: %Rrc",
2164 pEntry->pszName, rc);
2165 pEntry->uImageBase = uBaseAddress;
2166 pEntry->fValidBits = pfnGetImport == NULL;
2167 }
2168
2169 *ppbBits = pEntry->pbBits;
2170 return VINF_SUCCESS;
2171}
2172
2173
2174/**
2175 * Frees all resources associated with a cache entry and wipes the members
2176 * clean.
2177 *
2178 * @param pEntry The entry to delete.
2179 */
2180static void supHardNTLdrCacheDeleteEntry(PSUPHNTLDRCACHEENTRY pEntry)
2181{
2182 if (pEntry->pbBits)
2183 {
2184 RTMemFree(pEntry->pbBits);
2185 pEntry->pbBits = NULL;
2186 }
2187
2188 if (pEntry->hLdrMod != NIL_RTLDRMOD)
2189 {
2190 RTLdrClose(pEntry->hLdrMod);
2191 pEntry->hLdrMod = NIL_RTLDRMOD;
2192 pEntry->pNtViRdr = NULL;
2193 }
2194 else if (pEntry->pNtViRdr)
2195 {
2196 pEntry->pNtViRdr->Core.pfnDestroy(&pEntry->pNtViRdr->Core);
2197 pEntry->pNtViRdr = NULL;
2198 }
2199
2200 if (pEntry->hFile)
2201 {
2202 NtClose(pEntry->hFile);
2203 pEntry->hFile = NULL;
2204 }
2205
2206 pEntry->pszName = NULL;
2207 pEntry->fVerified = false;
2208 pEntry->fValidBits = false;
2209 pEntry->uImageBase = 0;
2210}
2211
2212#ifdef IN_RING3
2213
2214/**
2215 * Flushes the cache.
2216 *
2217 * This is called from one of two points in the hardened main code, first is
2218 * after respawning and the second is when we open the vboxdrv device for
2219 * unrestricted access.
2220 */
2221DECLHIDDEN(void) supR3HardenedWinFlushLoaderCache(void)
2222{
2223 uint32_t i = g_cSupNtVpLdrCacheEntries;
2224 while (i-- > 0)
2225 supHardNTLdrCacheDeleteEntry(&g_aSupNtVpLdrCacheEntries[i]);
2226 g_cSupNtVpLdrCacheEntries = 0;
2227}
2228
2229
2230/**
2231 * Searches the cache for a loader image.
2232 *
2233 * @returns Pointer to the cache entry if found, NULL if not.
2234 * @param pszName The name (from g_apszSupNtVpAllowedVmExes or
2235 * g_apszSupNtVpAllowedDlls).
2236 */
2237static PSUPHNTLDRCACHEENTRY supHardNtLdrCacheLookupEntry(const char *pszName)
2238{
2239 /*
2240 * Since the caller is supplying us a pszName from one of the two tables,
2241 * we can dispense with string compare and simply compare string pointers.
2242 */
2243 uint32_t i = g_cSupNtVpLdrCacheEntries;
2244 while (i-- > 0)
2245 if (g_aSupNtVpLdrCacheEntries[i].pszName == pszName)
2246 return &g_aSupNtVpLdrCacheEntries[i];
2247 return NULL;
2248}
2249
2250#endif /* IN_RING3 */
2251
2252static int supHardNtLdrCacheNewEntry(PSUPHNTLDRCACHEENTRY pEntry, const char *pszName, PUNICODE_STRING pUniStrPath,
2253 bool fDll, bool f32bitResourceDll, PRTERRINFO pErrInfo)
2254{
2255 /*
2256 * Open the image file.
2257 */
2258 HANDLE hFile = RTNT_INVALID_HANDLE_VALUE;
2259 IO_STATUS_BLOCK Ios = RTNT_IO_STATUS_BLOCK_INITIALIZER;
2260
2261 OBJECT_ATTRIBUTES ObjAttr;
2262 InitializeObjectAttributes(&ObjAttr, pUniStrPath, OBJ_CASE_INSENSITIVE, NULL /*hRootDir*/, NULL /*pSecDesc*/);
2263#ifdef IN_RING0
2264 ObjAttr.Attributes |= OBJ_KERNEL_HANDLE;
2265#endif
2266
2267 NTSTATUS rcNt = NtCreateFile(&hFile,
2268 GENERIC_READ | SYNCHRONIZE,
2269 &ObjAttr,
2270 &Ios,
2271 NULL /* Allocation Size*/,
2272 FILE_ATTRIBUTE_NORMAL,
2273 FILE_SHARE_READ,
2274 FILE_OPEN,
2275 FILE_NON_DIRECTORY_FILE | FILE_SYNCHRONOUS_IO_NONALERT,
2276 NULL /*EaBuffer*/,
2277 0 /*EaLength*/);
2278 if (NT_SUCCESS(rcNt))
2279 rcNt = Ios.Status;
2280 if (!NT_SUCCESS(rcNt))
2281 return supHardNtVpSetInfo1(pErrInfo, VERR_SUP_VP_IMAGE_FILE_OPEN_ERROR,
2282 "Error opening image for scanning: %#x (name %ls)", rcNt, pUniStrPath->Buffer);
2283
2284 /*
2285 * Figure out validation flags we'll be using and create the reader
2286 * for this image.
2287 */
2288 uint32_t fFlags = fDll
2289 ? SUPHNTVI_F_TRUSTED_INSTALLER_OR_SIMILAR_OWNER | SUPHNTVI_F_ALLOW_CAT_FILE_VERIFICATION
2290 : SUPHNTVI_F_REQUIRE_BUILD_CERT;
2291 if (f32bitResourceDll)
2292 fFlags |= SUPHNTVI_F_IGNORE_ARCHITECTURE;
2293
2294 PSUPHNTVIRDR pNtViRdr;
2295 int rc = supHardNtViRdrCreate(hFile, pUniStrPath->Buffer, fFlags, &pNtViRdr);
2296 if (RT_FAILURE(rc))
2297 {
2298 NtClose(hFile);
2299 return rc;
2300 }
2301
2302 /*
2303 * Finally, open the image with the loader
2304 */
2305 RTLDRMOD hLdrMod;
2306 RTLDRARCH enmArch = fFlags & SUPHNTVI_F_RC_IMAGE ? RTLDRARCH_X86_32 : RTLDRARCH_HOST;
2307 if (fFlags & SUPHNTVI_F_IGNORE_ARCHITECTURE)
2308 enmArch = RTLDRARCH_WHATEVER;
2309 rc = RTLdrOpenWithReader(&pNtViRdr->Core, RTLDR_O_FOR_VALIDATION, enmArch, &hLdrMod, pErrInfo);
2310 if (RT_FAILURE(rc))
2311 return supHardNtVpAddInfo1(pErrInfo, rc, "RTLdrOpenWithReader failed: %Rrc (Image='%ls').",
2312 rc, pUniStrPath->Buffer);
2313
2314 /*
2315 * Fill in the cache entry.
2316 */
2317 pEntry->pszName = pszName;
2318 pEntry->hLdrMod = hLdrMod;
2319 pEntry->pNtViRdr = pNtViRdr;
2320 pEntry->hFile = hFile;
2321 pEntry->pbBits = NULL;
2322 pEntry->fVerified = false;
2323 pEntry->fValidBits = false;
2324 pEntry->uImageBase = ~(uintptr_t)0;
2325
2326#ifdef IN_SUP_HARDENED_R3
2327 /*
2328 * Log the image timestamp when in the hardened exe.
2329 */
2330 uint64_t uTimestamp = 0;
2331 rc = RTLdrQueryProp(hLdrMod, RTLDRPROP_TIMESTAMP_SECONDS, &uTimestamp, sizeof(uint64_t));
2332 SUP_DPRINTF(("%s: timestamp %#llx (rc=%Rrc)\n", pszName, uTimestamp, rc));
2333#endif
2334
2335 return VINF_SUCCESS;
2336}
2337
2338#ifdef IN_RING3
2339/**
2340 * Opens a loader cache entry.
2341 *
2342 * Currently this is only used by the import code for getting NTDLL.
2343 *
2344 * @returns VBox status code.
2345 * @param pszName The DLL name. Must be one from the
2346 * g_apszSupNtVpAllowedDlls array.
2347 * @param ppEntry Where to return the entry we've opened/found.
2348 * @param pErrInfo Optional buffer where to return additional error
2349 * information.
2350 */
2351DECLHIDDEN(int) supHardNtLdrCacheOpen(const char *pszName, PSUPHNTLDRCACHEENTRY *ppEntry, PRTERRINFO pErrInfo)
2352{
2353 /*
2354 * Locate the dll.
2355 */
2356 uint32_t i = 0;
2357 while ( i < RT_ELEMENTS(g_apszSupNtVpAllowedDlls)
2358 && strcmp(pszName, g_apszSupNtVpAllowedDlls[i]))
2359 i++;
2360 if (i >= RT_ELEMENTS(g_apszSupNtVpAllowedDlls))
2361 return VERR_FILE_NOT_FOUND;
2362 pszName = g_apszSupNtVpAllowedDlls[i];
2363
2364 /*
2365 * Try the cache.
2366 */
2367 *ppEntry = supHardNtLdrCacheLookupEntry(pszName);
2368 if (*ppEntry)
2369 return VINF_SUCCESS;
2370
2371 /*
2372 * Not in the cache, so open it.
2373 * Note! We cannot assume that g_System32NtPath has been initialized at this point.
2374 */
2375 if (g_cSupNtVpLdrCacheEntries >= RT_ELEMENTS(g_aSupNtVpLdrCacheEntries))
2376 return VERR_INTERNAL_ERROR_3;
2377
2378 static WCHAR s_wszSystem32[] = L"\\SystemRoot\\System32\\";
2379 WCHAR wszPath[64];
2380 memcpy(wszPath, s_wszSystem32, sizeof(s_wszSystem32));
2381 RTUtf16CatAscii(wszPath, sizeof(wszPath), pszName);
2382
2383 UNICODE_STRING UniStr;
2384 UniStr.Buffer = wszPath;
2385 UniStr.Length = (USHORT)(RTUtf16Len(wszPath) * sizeof(WCHAR));
2386 UniStr.MaximumLength = UniStr.Length + sizeof(WCHAR);
2387
2388 int rc = supHardNtLdrCacheNewEntry(&g_aSupNtVpLdrCacheEntries[g_cSupNtVpLdrCacheEntries], pszName, &UniStr,
2389 true /*fDll*/, false /*f32bitResourceDll*/, pErrInfo);
2390 if (RT_SUCCESS(rc))
2391 {
2392 *ppEntry = &g_aSupNtVpLdrCacheEntries[g_cSupNtVpLdrCacheEntries];
2393 g_cSupNtVpLdrCacheEntries++;
2394 return VINF_SUCCESS;
2395 }
2396 return rc;
2397}
2398#endif /* IN_RING3 */
2399
2400
2401/**
2402 * Opens all the images with the IPRT loader, setting both, hFile, pNtViRdr and
2403 * hLdrMod for each image.
2404 *
2405 * @returns VBox status code.
2406 * @param pThis The process scanning state structure.
2407 */
2408static int supHardNtVpOpenImages(PSUPHNTVPSTATE pThis)
2409{
2410 unsigned i = pThis->cImages;
2411 while (i-- > 0)
2412 {
2413 PSUPHNTVPIMAGE pImage = &pThis->aImages[i];
2414
2415#ifdef VBOX_WITH_MINIMAL_HARDENING
2416 /* Only the process executable image is verified here, so don't bother open all the other ones. */
2417 if (pImage->fDll)
2418 continue;
2419#endif
2420
2421#ifdef IN_RING3
2422 /*
2423 * Try the cache first.
2424 */
2425 pImage->pCacheEntry = supHardNtLdrCacheLookupEntry(pImage->pszName);
2426 if (pImage->pCacheEntry)
2427 continue;
2428
2429 /*
2430 * Not in the cache, so load it into the cache.
2431 */
2432 if (g_cSupNtVpLdrCacheEntries >= RT_ELEMENTS(g_aSupNtVpLdrCacheEntries))
2433 return supHardNtVpSetInfo2(pThis, VERR_INTERNAL_ERROR_3, "Loader cache overflow.");
2434 pImage->pCacheEntry = &g_aSupNtVpLdrCacheEntries[g_cSupNtVpLdrCacheEntries];
2435#else
2436 /*
2437 * In ring-0 we don't have a cache at the moment (resource reasons), so
2438 * we have a static cache entry in each image structure that we use instead.
2439 */
2440 pImage->pCacheEntry = &pImage->CacheEntry;
2441#endif
2442
2443#ifndef VBOX_WITH_MINIMAL_HARDENING
2444 const char * const pszName = pImage->pszName;
2445#else
2446 const char * const pszName = "ignored.exe";
2447#endif
2448 int rc = supHardNtLdrCacheNewEntry(pImage->pCacheEntry, pszName, &pImage->Name.UniStr,
2449 pImage->fDll, pImage->f32bitResourceDll, pThis->pErrInfo);
2450 if (RT_FAILURE(rc))
2451 return rc;
2452#ifdef IN_RING3
2453 g_cSupNtVpLdrCacheEntries++;
2454#endif
2455 }
2456
2457 return VINF_SUCCESS;
2458}
2459
2460
2461/**
2462 * Check the integrity of the executable of the process.
2463 *
2464 * @returns VBox status code.
2465 * @param pThis The process scanning state structure. Details
2466 * about images are added to this. The hProcess
2467 * member holds the handle to the process that is
2468 * to be verified.
2469 */
2470static int supHardNtVpCheckExe(PSUPHNTVPSTATE pThis)
2471{
2472 /*
2473 * Make sure there is exactly one executable image.
2474 */
2475 unsigned cExecs = 0;
2476 unsigned iExe = ~0U;
2477 unsigned i = pThis->cImages;
2478 while (i-- > 0)
2479 {
2480 if (!pThis->aImages[i].fDll)
2481 {
2482 cExecs++;
2483 iExe = i;
2484 }
2485 }
2486 if (cExecs == 0)
2487 return supHardNtVpSetInfo2(pThis, VERR_SUP_VP_NO_FOUND_NO_EXE_MAPPING,
2488 "No executable mapping found in the virtual address space.");
2489 if (cExecs != 1)
2490 return supHardNtVpSetInfo2(pThis, VERR_SUP_VP_FOUND_MORE_THAN_ONE_EXE_MAPPING,
2491 "Found more than one executable mapping in the virtual address space.");
2492 PSUPHNTVPIMAGE pImage = &pThis->aImages[iExe];
2493
2494 /*
2495 * Check that it matches the executable image of the process.
2496 */
2497 int rc;
2498 ULONG cbUniStr = sizeof(UNICODE_STRING) + RTPATH_MAX * sizeof(RTUTF16);
2499 PUNICODE_STRING pUniStr = (PUNICODE_STRING)RTMemAllocZ(cbUniStr);
2500 if (!pUniStr)
2501 return supHardNtVpSetInfo2(pThis, VERR_SUP_VP_NO_MEMORY,
2502 "Error allocating %zu bytes for process name.", cbUniStr);
2503 ULONG cbIgn = 0;
2504 NTSTATUS rcNt = NtQueryInformationProcess(pThis->hProcess, ProcessImageFileName, pUniStr, cbUniStr - sizeof(WCHAR), &cbIgn);
2505 if (NT_SUCCESS(rcNt))
2506 {
2507 pUniStr->Buffer[pUniStr->Length / sizeof(WCHAR)] = '\0';
2508 if (supHardNtVpArePathsEqual(pUniStr, &pImage->Name.UniStr))
2509 rc = VINF_SUCCESS;
2510 else
2511 rc = supHardNtVpSetInfo2(pThis, VERR_SUP_VP_EXE_VS_PROC_NAME_MISMATCH,
2512 "Process image name does not match the exectuable we found: %ls vs %ls.",
2513 pUniStr->Buffer, pImage->Name.UniStr.Buffer);
2514 }
2515 else
2516 rc = supHardNtVpSetInfo2(pThis, VERR_SUP_VP_NT_QI_PROCESS_NM_ERROR,
2517 "NtQueryInformationProcess/ProcessImageFileName failed: %#x", rcNt);
2518 RTMemFree(pUniStr);
2519 if (RT_FAILURE(rc))
2520 return rc;
2521
2522 /*
2523 * Validate the signing of the executable image.
2524 * This will load the fDllCharecteristics and fImageCharecteristics members we use below.
2525 */
2526 rc = supHardNtVpVerifyImage(pThis, pImage);
2527 if (RT_FAILURE(rc))
2528 return rc;
2529
2530 /*
2531 * Check linking requirements.
2532 * This query is only available using the current process pseudo handle on
2533 * older windows versions. The cut-off seems to be Vista.
2534 */
2535 SECTION_IMAGE_INFORMATION ImageInfo;
2536 rcNt = NtQueryInformationProcess(pThis->hProcess, ProcessImageInformation, &ImageInfo, sizeof(ImageInfo), NULL);
2537 if (!NT_SUCCESS(rcNt))
2538 {
2539 if ( rcNt == STATUS_INVALID_PARAMETER
2540 && g_uNtVerCombined < SUP_NT_VER_VISTA
2541 && pThis->hProcess != NtCurrentProcess() )
2542 return VINF_SUCCESS;
2543 return supHardNtVpSetInfo2(pThis, VERR_SUP_VP_NT_QI_PROCESS_IMG_INFO_ERROR,
2544 "NtQueryInformationProcess/ProcessImageInformation failed: %#x hProcess=%#x",
2545 rcNt, pThis->hProcess);
2546 }
2547#ifndef VBOX_WITHOUT_WINDOWS_KERNEL_CODE_SIGNING_CERT /* A kernel code signing cert is only via way to use /IntegrityCheck. */
2548 if ( !(ImageInfo.DllCharacteristics & IMAGE_DLLCHARACTERISTICS_FORCE_INTEGRITY))
2549 return supHardNtVpSetInfo2(pThis, VERR_SUP_VP_EXE_MISSING_FORCE_INTEGRITY,
2550 "EXE DllCharacteristics=%#x, expected IMAGE_DLLCHARACTERISTICS_FORCE_INTEGRITY to be set.",
2551 ImageInfo.DllCharacteristics);
2552#endif
2553 if (!(ImageInfo.DllCharacteristics & IMAGE_DLLCHARACTERISTICS_DYNAMIC_BASE))
2554 return supHardNtVpSetInfo2(pThis, VERR_SUP_VP_EXE_MISSING_DYNAMIC_BASE,
2555 "EXE DllCharacteristics=%#x, expected IMAGE_DLLCHARACTERISTICS_DYNAMIC_BASE to be set.",
2556 ImageInfo.DllCharacteristics);
2557 if (!(ImageInfo.DllCharacteristics & IMAGE_DLLCHARACTERISTICS_NX_COMPAT))
2558 return supHardNtVpSetInfo2(pThis, VERR_SUP_VP_EXE_MISSING_NX_COMPAT,
2559 "EXE DllCharacteristics=%#x, expected IMAGE_DLLCHARACTERISTICS_NX_COMPAT to be set.",
2560 ImageInfo.DllCharacteristics);
2561
2562 if (pImage->fDllCharecteristics != ImageInfo.DllCharacteristics)
2563 return supHardNtVpSetInfo2(pThis, VERR_SUP_VP_DLL_CHARECTERISTICS_MISMATCH,
2564 "EXE Info.DllCharacteristics=%#x fDllCharecteristics=%#x.",
2565 ImageInfo.DllCharacteristics, pImage->fDllCharecteristics);
2566
2567 if (pImage->fImageCharecteristics != ImageInfo.ImageCharacteristics)
2568 return supHardNtVpSetInfo2(pThis, VERR_SUP_VP_DLL_CHARECTERISTICS_MISMATCH,
2569 "EXE Info.ImageCharacteristics=%#x fImageCharecteristics=%#x.",
2570 ImageInfo.ImageCharacteristics, pImage->fImageCharecteristics);
2571
2572 return VINF_SUCCESS;
2573}
2574
2575
2576#ifndef VBOX_WITH_MINIMAL_HARDENING
2577/**
2578 * Check the integrity of the DLLs found in the process.
2579 *
2580 * @returns VBox status code.
2581 * @param pThis The process scanning state structure. Details
2582 * about images are added to this. The hProcess
2583 * member holds the handle to the process that is
2584 * to be verified.
2585 */
2586static int supHardNtVpCheckDlls(PSUPHNTVPSTATE pThis)
2587{
2588 /*
2589 * Check for duplicate entries (paranoia).
2590 */
2591 uint32_t i = pThis->cImages;
2592 while (i-- > 1)
2593 {
2594 const char *pszName = pThis->aImages[i].pszName;
2595 uint32_t j = i;
2596 while (j-- > 0)
2597 if (pThis->aImages[j].pszName == pszName)
2598 return supHardNtVpSetInfo2(pThis, VERR_SUP_VP_DUPLICATE_DLL_MAPPING,
2599 "Duplicate image entries for %s: %ls and %ls",
2600 pszName, pThis->aImages[i].Name.UniStr.Buffer, pThis->aImages[j].Name.UniStr.Buffer);
2601 }
2602
2603 /*
2604 * Check that both ntdll and kernel32 are present.
2605 * ASSUMES the entries in g_apszSupNtVpAllowedDlls are all lower case.
2606 */
2607 uint32_t iNtDll = UINT32_MAX;
2608 uint32_t iKernel32 = UINT32_MAX;
2609 i = pThis->cImages;
2610 while (i-- > 0)
2611 if (suplibHardenedStrCmp(pThis->aImages[i].pszName, "ntdll.dll") == 0)
2612 iNtDll = i;
2613 else if (suplibHardenedStrCmp(pThis->aImages[i].pszName, "kernel32.dll") == 0)
2614 iKernel32 = i;
2615 if (iNtDll == UINT32_MAX)
2616 return supHardNtVpSetInfo2(pThis, VERR_SUP_VP_NO_NTDLL_MAPPING,
2617 "The process has no NTDLL.DLL.");
2618 if (iKernel32 == UINT32_MAX && ( pThis->enmKind == SUPHARDNTVPKIND_SELF_PURIFICATION
2619 || pThis->enmKind == SUPHARDNTVPKIND_SELF_PURIFICATION_LIMITED))
2620 return supHardNtVpSetInfo2(pThis, VERR_SUP_VP_NO_KERNEL32_MAPPING,
2621 "The process has no KERNEL32.DLL.");
2622 else if (iKernel32 != UINT32_MAX && pThis->enmKind == SUPHARDNTVPKIND_CHILD_PURIFICATION)
2623 return supHardNtVpSetInfo2(pThis, VERR_SUP_VP_KERNEL32_ALREADY_MAPPED,
2624 "The process already has KERNEL32.DLL loaded.");
2625
2626 /*
2627 * Verify that the DLLs are correctly signed (by MS).
2628 */
2629 i = pThis->cImages;
2630 while (i-- > 0)
2631 {
2632 int rc = supHardNtVpVerifyImage(pThis, &pThis->aImages[i]);
2633 if (RT_FAILURE(rc))
2634 return rc;
2635 }
2636
2637 return VINF_SUCCESS;
2638}
2639#endif /* !VBOX_WITH_MINIMAL_HARDENING */
2640
2641
2642#ifdef IN_RING3
2643/**
2644 * Verifies that we don't have any inheritable handles around, other than a few
2645 * ones for file and event objects.
2646 *
2647 * When finding an inheritable handle of a different type, it will change it to
2648 * non-inhertiable. This must NOT be called in the final process prior to
2649 * opening the device!
2650 *
2651 * @returns VBox status code
2652 * @param pThis The process scanning state structure.
2653 */
2654static int supHardNtVpCheckHandles(PSUPHNTVPSTATE pThis)
2655{
2656 SUP_DPRINTF(("supHardNtVpCheckHandles:\n"));
2657
2658 /*
2659 * Take a snapshot of all the handles in the system.
2660 * (Because the current process handle snapshot was added in Windows 8,
2661 * so we cannot use that yet.)
2662 */
2663 uint32_t cbBuf = _256K;
2664 uint8_t *pbBuf = (uint8_t *)RTMemAlloc(cbBuf);
2665 ULONG cbNeeded = cbBuf;
2666 NTSTATUS rcNt = NtQuerySystemInformation(SystemExtendedHandleInformation, pbBuf, cbBuf, &cbNeeded);
2667 if (!NT_SUCCESS(rcNt))
2668 {
2669 while ( rcNt == STATUS_INFO_LENGTH_MISMATCH
2670 && cbNeeded > cbBuf
2671 && cbBuf <= _32M)
2672 {
2673 cbBuf = RT_ALIGN_32(cbNeeded + _4K, _64K);
2674 RTMemFree(pbBuf);
2675 pbBuf = (uint8_t *)RTMemAlloc(cbBuf);
2676 if (!pbBuf)
2677 return supHardNtVpSetInfo2(pThis, VERR_SUP_VP_NO_MEMORY, "Failed to allocate %zu bytes querying handles.", cbBuf);
2678 rcNt = NtQuerySystemInformation(SystemExtendedHandleInformation, pbBuf, cbBuf, &cbNeeded);
2679 }
2680 if (!NT_SUCCESS(rcNt))
2681 {
2682 RTMemFree(pbBuf);
2683 return supHardNtVpSetInfo2(pThis, VERR_SUP_VP_NO_MEMORY, "Failed to allocate %zu bytes querying handles.", cbBuf);
2684 }
2685 }
2686
2687 /*
2688 * Examine the snapshot for handles for this process.
2689 */
2690 int rcRet = VINF_SUCCESS;
2691 HANDLE const idProcess = RTNtCurrentTeb()->ClientId.UniqueProcess;
2692 SYSTEM_HANDLE_INFORMATION_EX const *pInfo = (SYSTEM_HANDLE_INFORMATION_EX const *)pbBuf;
2693 ULONG_PTR i = pInfo->NumberOfHandles;
2694 AssertRelease(RT_UOFFSETOF_DYN(SYSTEM_HANDLE_INFORMATION_EX, Handles[i]) == cbNeeded);
2695 while (i-- > 0)
2696 {
2697 SYSTEM_HANDLE_ENTRY_INFO_EX const *pHandleInfo = &pInfo->Handles[i];
2698 if ( (pHandleInfo->HandleAttributes & OBJ_INHERIT)
2699 && pHandleInfo->UniqueProcessId == idProcess)
2700 {
2701 ULONG cbNeeded2 = 0;
2702 rcNt = NtQueryObject(pHandleInfo->HandleValue, ObjectTypeInformation,
2703 pThis->abMemory, sizeof(pThis->abMemory), &cbNeeded2);
2704 if (NT_SUCCESS(rcNt))
2705 {
2706 POBJECT_TYPE_INFORMATION pTypeInfo = (POBJECT_TYPE_INFORMATION)pThis->abMemory;
2707 if ( pTypeInfo->TypeName.Length == sizeof(L"File") - sizeof(wchar_t)
2708 && memcmp(pTypeInfo->TypeName.Buffer, L"File", sizeof(L"File") - sizeof(wchar_t)) == 0)
2709 SUP_DPRINTF(("supHardNtVpCheckHandles: Inheritable file handle: %p\n", pHandleInfo->HandleValue));
2710 else if ( pTypeInfo->TypeName.Length == sizeof(L"Event") - sizeof(wchar_t)
2711 && memcmp(pTypeInfo->TypeName.Buffer, L"Event", sizeof(L"Event") - sizeof(wchar_t)) == 0)
2712 SUP_DPRINTF(("supHardNtVpCheckHandles: Inheritable event handle: %p\n", pHandleInfo->HandleValue));
2713 else
2714 {
2715 OBJECT_HANDLE_FLAG_INFORMATION SetInfo;
2716 SetInfo.Inherit = FALSE;
2717 SetInfo.ProtectFromClose = FALSE;
2718 rcNt = NtSetInformationObject(pHandleInfo->HandleValue, ObjectHandleFlagInformation,
2719 &SetInfo, sizeof(SetInfo));
2720 if (NT_SUCCESS(rcNt))
2721 {
2722 SUP_DPRINTF(("supHardNtVpCheckHandles: Marked %ls handle non-inheritable: %p\n",
2723 pTypeInfo->TypeName.Buffer, pHandleInfo->HandleValue));
2724 pThis->cFixes++;
2725 }
2726 else
2727 {
2728 rcRet = supHardNtVpSetInfo2(pThis, VERR_SUP_VP_SET_HANDLE_NOINHERIT,
2729 "NtSetInformationObject(%p,,,) -> %#x", pHandleInfo->HandleValue, rcNt);
2730 break;
2731 }
2732 }
2733 }
2734 else
2735 {
2736 rcRet = supHardNtVpSetInfo2(pThis, VERR_SUP_VP_QUERY_HANDLE_TYPE,
2737 "NtQueryObject(%p,,,,) -> %#x", pHandleInfo->HandleValue, rcNt);
2738 break;
2739 }
2740
2741 }
2742 }
2743 RTMemFree(pbBuf);
2744 return rcRet;
2745}
2746#endif /* IN_RING3 */
2747
2748
2749/**
2750 * Verifies the given process.
2751 *
2752 * The following requirements are checked:
2753 * - The process only has one thread, the calling thread.
2754 * - The process has no debugger attached.
2755 * - The executable image of the process is verified to be signed with
2756 * certificate known to this code at build time.
2757 * - The executable image is one of a predefined set.
2758 * - The process has only a very limited set of system DLLs loaded.
2759 * - The system DLLs signatures check out fine.
2760 * - The only executable memory in the process belongs to the system DLLs and
2761 * the executable image.
2762 *
2763 * @returns VBox status code.
2764 * @param hProcess The process to verify.
2765 * @param hThread A thread in the process (the caller).
2766 * @param enmKind The kind of process verification to perform.
2767 * @param fFlags Valid combination of SUPHARDNTVP_F_XXX flags.
2768 * @param pErrInfo Pointer to error info structure. Optional.
2769 * @param pcFixes Where to return the number of fixes made during
2770 * purification. Optional.
2771 */
2772DECLHIDDEN(int) supHardenedWinVerifyProcess(HANDLE hProcess, HANDLE hThread, SUPHARDNTVPKIND enmKind, uint32_t fFlags,
2773 uint32_t *pcFixes, PRTERRINFO pErrInfo)
2774{
2775 RT_NOREF(hThread);
2776 if (pcFixes)
2777 *pcFixes = 0;
2778
2779 /*
2780 * Some basic checks regarding threads and debuggers. We don't need
2781 * allocate any state memory for these.
2782 */
2783 int rc = VINF_SUCCESS;
2784#ifndef VBOX_WITH_MINIMAL_HARDENING
2785 if ( enmKind != SUPHARDNTVPKIND_CHILD_PURIFICATION
2786 && enmKind != SUPHARDNTVPKIND_SELF_PURIFICATION_LIMITED)
2787 rc = supHardNtVpThread(hProcess, hThread, pErrInfo);
2788 if (RT_SUCCESS(rc))
2789 rc = supHardNtVpDebugger(hProcess, pErrInfo);
2790 if (RT_SUCCESS(rc))
2791#endif /* !VBOX_WITH_MINIMAL_HARDENING */
2792 {
2793 /*
2794 * Allocate and initialize memory for the state.
2795 */
2796 PSUPHNTVPSTATE pThis = (PSUPHNTVPSTATE)RTMemAllocZ(sizeof(*pThis));
2797 if (pThis)
2798 {
2799 pThis->enmKind = enmKind;
2800 pThis->fFlags = fFlags;
2801 pThis->rcResult = VINF_SUCCESS;
2802 pThis->hProcess = hProcess;
2803 pThis->pErrInfo = pErrInfo;
2804
2805 /*
2806 * Perform the verification.
2807 */
2808 rc = supHardNtVpScanVirtualMemory(pThis, hProcess);
2809 if (RT_SUCCESS(rc))
2810 rc = supHardNtVpOpenImages(pThis);
2811 if (RT_SUCCESS(rc))
2812 rc = supHardNtVpCheckExe(pThis);
2813#ifndef VBOX_WITH_MINIMAL_HARDENING
2814 if (RT_SUCCESS(rc))
2815 rc = supHardNtVpCheckDlls(pThis);
2816# ifdef IN_RING3
2817 if (enmKind == SUPHARDNTVPKIND_SELF_PURIFICATION_LIMITED)
2818 rc = supHardNtVpCheckHandles(pThis);
2819# endif
2820#endif /* !VBOX_WITH_MINIMAL_HARDENING */
2821
2822
2823 if (pcFixes)
2824 *pcFixes = pThis->cFixes;
2825
2826 /*
2827 * Clean up the state.
2828 */
2829#ifdef IN_RING0
2830 for (uint32_t i = 0; i < pThis->cImages; i++)
2831 supHardNTLdrCacheDeleteEntry(&pThis->aImages[i].CacheEntry);
2832#endif
2833 RTMemFree(pThis);
2834 }
2835 else
2836 rc = supHardNtVpSetInfo1(pErrInfo, VERR_SUP_VP_NO_MEMORY_STATE,
2837 "Failed to allocate %zu bytes for state structures.", sizeof(*pThis));
2838 }
2839 return rc;
2840}
2841
注意: 瀏覽 TracBrowser 來幫助您使用儲存庫瀏覽器

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