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