SUPR3HardenedMain-win.cpp revision cf0e96b2c5a08292c6d13e4fdcb2d9518d1983e8
/* $Id$ */
/** @file
* VirtualBox Support Library - Hardened main(), windows bits.
*/
/*
* Copyright (C) 2006-2014 Oracle Corporation
*
* This file is part of VirtualBox Open Source Edition (OSE), as
* available from http://www.virtualbox.org. This file is free software;
* General Public License (GPL) as published by the Free Software
* Foundation, in version 2 as it comes in the "COPYING" file of the
* VirtualBox OSE distribution. VirtualBox OSE is distributed in the
* hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
*
* The contents of this file may alternatively be used under the terms
* of the Common Development and Distribution License Version 1.0
* (CDDL) only, as it comes in the "COPYING.CDDL" file of the
* VirtualBox OSE distribution, in which case the provisions of the
* CDDL are applicable instead of those of the GPL.
*
* You may elect to license modified versions of this file under the
* terms and conditions of either the GPL or the CDDL or both.
*/
/*******************************************************************************
* Header Files *
*******************************************************************************/
#include <AccCtrl.h>
#include <AclApi.h>
#ifndef PROCESS_SET_LIMITED_INFORMATION
# define PROCESS_SET_LIMITED_INFORMATION 0x2000
#endif
# define LOAD_LIBRARY_SEARCH_APPLICATION_DIR 0x200
# define LOAD_LIBRARY_SEARCH_SYSTEM32 0x800
#endif
#include <iprt/initterm.h>
#include "SUPLibInternal.h"
#include "win/SUPHardenedVerify-win.h"
#include "../SUPDrvIOC.h"
#ifndef IMAGE_SCN_TYPE_NOLOAD
# define IMAGE_SCN_TYPE_NOLOAD 0x00000002
#endif
/*******************************************************************************
* Defined Constants And Macros *
*******************************************************************************/
/** The first argument of a respawed stub when respawned for the first time.
* This just needs to be unique enough to avoid most confusion with real
* executable names, there are other checks in place to make sure we've respanwed. */
#define SUPR3_RESPAWN_1_ARG0 "60eaff78-4bdd-042d-2e72-669728efd737-suplib-2ndchild"
/** The first argument of a respawed stub when respawned for the second time.
* This just needs to be unique enough to avoid most confusion with real
* executable names, there are other checks in place to make sure we've respanwed. */
#define SUPR3_RESPAWN_2_ARG0 "60eaff78-4bdd-042d-2e72-669728efd737-suplib-3rdchild"
/** Unconditional assertion. */
#define SUPR3HARDENED_ASSERT(a_Expr) \
do { \
if (!(a_Expr)) \
} while (0)
/** Unconditional assertion of NT_SUCCESS. */
#define SUPR3HARDENED_ASSERT_NT_SUCCESS(a_Expr) \
do { \
if (!NT_SUCCESS(rcNtAssert)) \
} while (0)
/** Unconditional assertion of a WIN32 API returning non-FALSE. */
#define SUPR3HARDENED_ASSERT_WIN32_SUCCESS(a_Expr) \
do { \
} while (0)
/*******************************************************************************
* Structures and Typedefs *
*******************************************************************************/
/**
* Security descriptor cleanup structure.
*/
typedef struct MYSECURITYCLEANUP
{
union
{
union
{
} Acl;
/** Pointer to security cleanup structure. */
typedef MYSECURITYCLEANUP *PMYSECURITYCLEANUP;
/**
* Image verifier cache entry.
*/
typedef struct VERIFIERCACHEENTRY
{
/** Pointer to the next entry with the same hash value. */
struct VERIFIERCACHEENTRY * volatile pNext;
/** Next entry in the WinVerifyTrust todo list. */
struct VERIFIERCACHEENTRY * volatile pNextTodoWvt;
/** The file handle. */
/** If fIndexNumber is set, this is an file system internal file identifier. */
/** The path hash value. */
/** The verification result. */
int rc;
/** Used for shutting up errors after a while. */
uint32_t volatile cErrorHits;
/** The validation flags (for WinVerifyTrust retry). */
/** Whether IndexNumber is valid */
bool fIndexNumberValid;
/** Whether verified by WinVerifyTrust. */
bool volatile fWinVerifyTrust;
/** cwcPath * sizeof(RTUTF16). */
/** The full path of this entry (variable size). */
/** Pointer to an image verifier path entry. */
typedef VERIFIERCACHEENTRY *PVERIFIERCACHEENTRY;
/**
* Name of an import DLL that we need to check out.
*/
typedef struct VERIFIERCACHEIMPORT
{
/** Pointer to the next DLL in the list. */
struct VERIFIERCACHEIMPORT * volatile pNext;
/** The length of pwszAltSearchDir if available. */
/** This points the directory containing the DLL needing it, this will be
* NULL for a System32 DLL. */
/** The name of the import DLL (variable length). */
char szName[1];
/** Pointer to a import DLL that needs checking out. */
typedef VERIFIERCACHEIMPORT *PVERIFIERCACHEIMPORT;
/*******************************************************************************
* Global Variables *
*******************************************************************************/
/** @name Global variables initialized by suplibHardenedWindowsMain.
* @{ */
/** Combined windows NT version number. See SUP_MAKE_NT_VER_COMBINED. */
uint32_t g_uNtVerCombined = 0;
/** Count calls to the special main function for linking santity checks. */
static uint32_t volatile g_cSuplibHardenedWindowsMainCalls;
/** The UTF-16 windows path to the executable. */
/** The NT path of the executable. */
/** The offset into g_SupLibHardenedExeNtPath of the executable name (WCHAR,
* not byte). This also gives the length of the exectuable directory path,
* including a trailing slash. */
/** @} */
/** @name Hook related variables.
* @{ */
/** The jump back address of the patched NtCreateSection. */
/** Pointer to the bit of assembly code that will perform the original
* NtCreateSection operation. */
#if 0
/** The jump back address of the patched LdrLoadDll. */
#endif
/** Pointer to the bit of assembly code that will perform the original
* LdrLoadDll operation. */
/** The hash table of verifier cache . */
/** Queue of cached images which needs WinVerifyTrust to check them. */
/** Queue of cached images which needs their imports checked. */
/** @ */
/** Static error info structure used during init. */
static RTERRINFOSTATIC g_ErrInfoStatic;
/** In the assembly file. */
/** Whether we've patched our own LdrInitializeThunk or not. We do this to
* disable thread creation. */
static bool g_fSupInitThunkSelfPatched;
/** The backup of our own LdrInitializeThunk code, for enabling and disabling
* thread creation in this process. */
/*******************************************************************************
* Internal Functions *
*******************************************************************************/
static NTSTATUS supR3HardenedScreenImage(HANDLE hFile, bool fImage, PULONG pfAccess, PULONG pfProtect,
bool *pfQuietFailure);
#ifdef RT_ARCH_AMD64
# include "NtCreateSection-template-amd64-syscall-type-1.h"
#endif
#ifdef RT_ARCH_X86
# include "NtCreateSection-template-x86-syscall-type-1.h"
#endif
/**
* Simple wide char search routine.
*
* @returns Pointer to the first location of @a wcNeedle in @a pwszHaystack.
* NULL if not found.
* @param pwszHaystack Pointer to the string that should be searched.
* @param wcNeedle The character to search for.
*/
{
for (;;)
{
return (PRTUTF16)pwszHaystack;
if (wcCur == '\0')
return NULL;
pwszHaystack++;
}
}
/**
* Simple wide char string length routine.
*
* @returns The number of characters in the given string. (Excludes the
* terminator.)
* @param pwsz The string.
*/
{
while (*pwszCur != '\0')
pwszCur++;
}
/**
* Allocate zero filled memory on the heap.
*
* @returns Pointer to the memory. Will never return NULL, triggers a fatal
* error instead.
* @param cb The number of bytes to allocate.
*/
{
if (!pv)
return pv;
}
/**
* Reallocates memory on the heap.
*
* @returns Pointer to the resized memory block. Will never return NULL,
* triggers a fatal error instead.
* @param pvOld The old memory block.
* @param cbNew The new block size.
*/
{
if (!pvOld)
return suplibHardenedAllocZ(cbNew);
if (!pv)
return pv;
}
/**
* Frees memory allocated by suplibHardenedAlloc, suplibHardenedAllocZ or
* suplibHardenedReAlloc.
*
* @param pv Pointer to the memeory to be freed.
*/
{
if (pv)
}
/**
* Wrapper around LoadLibraryEx that deals with the UTF-8 to UTF-16 conversion
* and supplies the right flags.
*
* @returns Module handle on success, NULL on failure.
* @param pszName The full path to the DLL.
* @param fSystem32Only Whether to only look for imports in the system32
* directory. If set to false, the application
* directory is also searched.
*/
{
if (RT_SUCCESS(rc))
{
while (*pwszPath)
{
if (*pwszPath == '/')
*pwszPath = '\\';
pwszPath++;
}
{
if (!fSystem32Only)
}
/* Vista, W7, W2K8R might not work without KB2533623, so retry with no flags. */
if ( !pvRet
&& fFlags
&& GetLastError() == ERROR_INVALID_PARAMETER)
return pvRet;
}
return NULL;
}
/**
* Gets the internal index number of the file.
*
* @returns True if we got an index number, false if not.
* @param hFile The file in question.
* @param pIndexNumber where to return the index number.
*/
{
NTSTATUS rcNt = NtQueryInformationFile(hFile, &Ios, pIndexNumber, sizeof(*pIndexNumber), FileInternalInformation);
if (NT_SUCCESS(rcNt))
#ifdef DEBUG_bird
if (!NT_SUCCESS(rcNt))
__debugbreak();
#endif
}
/**
* Calculates the hash value for the given UTF-16 path string.
*
* @returns Hash value.
* @param pUniStr String to hash.
*/
{
while (cwcLeft-- > 0)
{
if (wc < 0x80)
}
return uHash;
}
/**
* Calculates the hash value for a directory + filename combo as if they were
* one single string.
*
* @returns Hash value.
* @param pawcDir The directory name.
* @param cwcDir The length of the directory name. RTSTR_MAX if
* not available.
* @param pszName The import name (UTF-8).
*/
static uint32_t supR3HardenedWinVerifyCacheHashDirAndFile(PCRTUTF16 pawcDir, uint32_t cwcDir, const char *pszName)
{
while (cwcDir-- > 0)
{
if (wc < 0x80)
}
unsigned char ch = '\\';
{
}
return uHash;
}
/**
* Verify string cache compare function.
*
* @returns true if the strings match, false if not.
* @param pawcLeft The left hand string.
* @param pawcRight The right hand string.
* @param cwcToCompare The number of chars to compare.
*/
static bool supR3HardenedWinVerifyCacheIsMatch(PCRTUTF16 pawcLeft, PCRTUTF16 pawcRight, uint32_t cwcToCompare)
{
/* Try a quick memory compare first. */
return true;
/* Slow char by char compare. */
while (cwcToCompare-- > 0)
{
{
return false;
}
}
return true;
}
/**
* Inserts the given verifier result into the cache.
*
* @param pUniStr The full path of the image.
* @param hFile The file handle - must either be entered into
* the cache or closed.
* @param rc The verifier result.
* @param fWinVerifyTrust Whether verified by WinVerifyTrust or not.
* @param fFlags The image verification flags.
*/
{
/*
* Allocate and initalize a new entry.
*/
if (pEntry)
{
pEntry->cErrorHits = 0;
/*
* Try insert it, careful with concurrent code as well as potential duplicates.
*/
for (;;)
{
{
if (!fWinVerifyTrust)
do
return;
}
if (!pOther)
continue;
&& supR3HardenedWinVerifyCacheIsMatch(pOther->wszPath, pEntry->wszPath, pEntry->cbPath / sizeof(RTUTF16)))
break;
}
/* Duplicate entry (may happen due to races). */
}
}
/**
* Looks up an entry in the verifier hash table.
*
* @return Pointer to the entry on if found, NULL if not.
* @param pUniStr The full path of the image.
* @param hFile The file handle.
*/
static PVERIFIERCACHEENTRY supR3HardenedWinVerifyCacheLookup(PCUNICODE_STRING pUniStr, HANDLE hFile)
{
while (pCur)
{
{
if (!pCur->fIndexNumberValid)
return pCur;
if ( fIndexNumberValid
return pCur;
#ifdef DEBUG_bird
__debugbreak();
#endif
}
}
return NULL;
}
/**
* Looks up an import DLL in the verifier hash table.
*
* @return Pointer to the entry on if found, NULL if not.
* @param pawcDir The directory name.
* @param cwcDir The length of the directory name.
* @param pszName The import name (UTF-8).
*/
static PVERIFIERCACHEENTRY supR3HardenedWinVerifyCacheLookupImport(PCRTUTF16 pawcDir, uint32_t cwcDir, const char *pszName)
{
while (pCur)
{
{
{
{
{
return pCur;
}
}
}
}
}
return NULL;
}
/**
* Schedules the import DLLs for verification and entry into the cache.
*
* @param hLdrMod The loader module which imports should be
* scheduled for verification.
* @param pwszName The full NT path of the module.
*/
{
/*
* Any imports?
*/
int rc = RTLdrQueryPropEx(hLdrMod, RTLDRPROP_IMPORT_COUNT, NULL /*pvBits*/, &cImports, sizeof(cImports), NULL);
if (RT_SUCCESS(rc))
{
if (cImports)
{
/*
* Figure out the DLL directory from pwszName.
*/
uint32_t i = 0;
cwcDir = i - 1;
/*
* Enumerate the imports.
*/
for (i = 0; i < cImports; i++)
{
union
{
char szName[256];
} uBuf;
rc = RTLdrQueryPropEx(hLdrMod, RTLDRPROP_IMPORT_MODULE, NULL /*pvBits*/, &uBuf, sizeof(uBuf), NULL);
if (RT_SUCCESS(rc))
{
/*
* Skip kernel32, ntdll and API set stuff.
*/
{
continue;
}
/*
* Skip to the next one if it's already in the cache.
*/
{
SUP_DPRINTF(("supR3HardenedWinVerifyCacheScheduleImports: '%s' cached for system32\n", uBuf.szName));
continue;
}
{
continue;
}
{
SUP_DPRINTF(("supR3HardenedWinVerifyCacheScheduleImports: '%s' cached for dll dir\n", uBuf.szName));
continue;
}
/* We could skip already scheduled modules, but that'll require serialization and extra work... */
/*
* Add it to the todo list.
*/
SUP_DPRINTF(("supR3HardenedWinVerifyCacheScheduleImports: Import todo: #%u '%s'.\n", i, uBuf.szName));
PVERIFIERCACHEIMPORT pImport = (PVERIFIERCACHEIMPORT)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, cbNeeded);
if (pImport)
{
/* Init it. */
if (!pawcDir)
{
pImport->cwcAltSearchDir = 0;
}
else
{
}
/* Insert it. */
do
}
}
else
}
}
else
}
else
}
/**
* Processes the list of import todos.
*/
static void supR3HardenedWinVerifyCacheProcessImportTodos(void)
{
/*
* Work until we've got nothing more todo.
*/
for (;;)
{
PVERIFIERCACHEIMPORT pTodo = ASMAtomicXchgPtrT(&g_pVerifierCacheTodoImports, NULL, PVERIFIERCACHEIMPORT);
if (!pTodo)
break;
do
{
/*
* Not in the cached already?
*/
&& ( pCur->cwcAltSearchDir == 0
|| !supR3HardenedWinVerifyCacheLookupImport(pCur->pwszAltSearchDir, pCur->cwcAltSearchDir, pCur->szName)) )
{
/*
* Try locate the imported DLL and open it.
*/
/*
* Check for DLL isolation / redirection / mapping.
*/
if (RT_SUCCESS(rc))
{
UniStrStatic.Length = 0;
NULL /*pNewFlags*/,
NULL /*pcbFilename*/,
NULL /*pcbNeeded*/);
if (NT_SUCCESS(rcNt))
{
&ObjAttr,
&Ios,
NULL /* Allocation Size*/,
NULL /*EaBuffer*/,
0 /*EaLength*/);
if (NT_SUCCESS(rcNt))
if (!NT_SUCCESS(rcNt))
}
}
else
SUP_DPRINTF(("supR3HardenedWinVerifyCacheProcessImportTodos: RTStrToUtf16Ex #1 failed: %Rrc\n", rc));
/*
* If not something that gets remapped, do the half normal searching we need.
*/
if (hFile == INVALID_HANDLE_VALUE)
{
struct
{
{
};
/* Search System32 first, unless it's a 'V*' or 'm*' name, the latter for msvcrt. */
{
}
{
{
if (RT_SUCCESS(rc))
{
InitializeObjectAttributes(&ObjAttr, &NtName, OBJ_CASE_INSENSITIVE, NULL /*hRootDir*/, NULL /*pSecDesc*/);
&ObjAttr,
&Ios,
NULL /* Allocation Size*/,
NULL /*EaBuffer*/,
0 /*EaLength*/);
if (NT_SUCCESS(rcNt))
if (NT_SUCCESS(rcNt))
break;
}
else
SUP_DPRINTF(("supR3HardenedWinVerifyCacheProcessImportTodos: RTStrToUtf16Ex #2 failed: %Rrc\n", rc));
}
}
}
/*
* If we successfully opened it, verify it and cache the result.
*/
if (hFile != INVALID_HANDLE_VALUE)
{
SUP_DPRINTF(("supR3HardenedWinVerifyCacheProcessImportTodos: '%s' -> '%ls'\n", pCur->szName, wszPath));
bool fCallRealApi = false;
}
else
SUP_DPRINTF(("supR3HardenedWinVerifyCacheProcessImportTodos: Failed to locate '%s'\n", pCur->szName));
}
else
SUP_DPRINTF(("supR3HardenedWinVerifyCacheProcessImportTodos: '%s' is in the cache.\n", pCur->szName));
} while (pTodo);
}
}
/**
* Processes the list of WinVerifyTrust todos.
*/
static void supR3HardenedWinVerifyCacheProcessWvtTodos(void)
{
/*
* Work until we've got nothing more todo.
*/
for (;;)
{
break;
if (!pTodo)
break;
do
{
if ( !pCur->fWinVerifyTrust
{
bool fWinVerifyTrust = false;
{
SUP_DPRINTF(("supR3HardenedWinVerifyCacheProcessWvtTodos: %d (was %d) fWinVerifyTrust=%d for '%ls'\n",
pCur->fWinVerifyTrust = true;
}
else
{
/* Retry it at a later time. */
SUP_DPRINTF(("supR3HardenedWinVerifyCacheProcessWvtTodos: %d (was %d) fWinVerifyTrust=%d for '%ls' [rescheduled]\n",
if (!pReschedule)
}
}
/* else: already processed. */
} while (pTodo);
}
/*
* Anything to reschedule.
*/
if (pReschedule)
{
do
}
}
/**
* Checks whether the path could be containing alternative 8.3 names generated
* by NTFS, FAT, or other similar file systems.
*
* @returns Pointer to the first component that might be an 8.3 name, NULL if
* not 8.3 path.
* @param pwszPath The path to check.
*/
{
for (;;)
{
if (wc == '~')
{
/* Could check more here before jumping to conclusions... */
}
else if (wc == 0)
break;
}
return NULL;
}
/**
* Fixes up a path possibly containing one or more alternative 8-dot-3 style
* components.
*
* The path is fixed up in place. Errors are ignored.
*
* @param hFile The handle to the file which path we're fixing up.
* @param pUniStr The path to fix up. MaximumLength is the max buffer
* length.
*/
{
/*
* We could use FileNormalizedNameInformation here and slap the volume device
* path in front of the result, but it's only supported since windows 8.0
* according to some docs... So we expand all supicious names.
*/
while (*pwszFix)
{
break;
pwszFixEnd++;
if (wc == '\0')
break;
InitializeObjectAttributes(&ObjAttr, &NtDir, OBJ_CASE_INSENSITIVE, NULL /*hRootDir*/, NULL /*pSecDesc*/);
&ObjAttr,
&Ios,
NULL /* Allocation Size*/,
NULL /*EaBuffer*/,
0 /*EaLength*/);
if (NT_SUCCESS(rcNt))
{
union
{
} uBuf;
NULL /* Event */,
NULL /* ApcRoutine */,
NULL /* ApcContext */,
&Ios,
&uBuf,
FALSE /*ReturnSingleEntry*/,
FALSE /*RestartScan */);
if (NT_SUCCESS(rcNt) && uBuf.Info.NextEntryOffset == 0) /* There shall only be one entry matching... */
{
while (offName > 0 && uBuf.Info.FileName[offName - 1] != '\\' && uBuf.Info.FileName[offName - 1] != '/')
offName--;
if (cwcNameOld == cwcNameNew)
<= pUniStr->MaximumLength)
{
pwszFixEnd -= cwcNameOld;
pwszFixEnd -= cwcNameNew;
}
/* else: ignore overflow. */
}
/* else: ignore failure. */
}
/* Advance */
}
}
static NTSTATUS supR3HardenedScreenImage(HANDLE hFile, bool fImage, PULONG pfAccess, PULONG pfProtect,
bool *pfQuietFailure)
{
*pfCallRealApi = false;
if (pfQuietFailure)
*pfQuietFailure = false;
/*
* Query the name of the file, making sure to zero terminator the
* string. (2nd half of buffer is used for error info, see below.)
*/
union
{
} uBuf;
NTSTATUS rcNt = NtQueryObject(hFile, ObjectNameInformation, &uBuf, sizeof(uBuf) - sizeof(WCHAR) - 128, &cbNameBuf);
if (!NT_SUCCESS(rcNt))
{
supR3HardenedError(VINF_SUCCESS, false,
"supR3HardenedScreenImage/%s: NtQueryObject -> %#x (fImage=%d fProtect=%#x fAccess=%#x)\n",
return rcNt;
}
{
}
/*
* Check the cache.
*/
if (pCacheHit)
{
/* If we haven't done the WinVerifyTrust thing, do it if we can. */
if ( !pCacheHit->fWinVerifyTrust
{
if (!fAvoidWinVerifyTrust)
{
SUP_DPRINTF(("supR3HardenedScreenImage/%s: cache hit (%Rrc) on %ls [redoing WinVerifyTrust]\n",
bool fWinVerifyTrust = false;
int rc = supHardenedWinVerifyImageTrust(pCacheHit->hFile, pCacheHit->wszPath, pCacheHit->fFlags, pCacheHit->rc,
{
SUP_DPRINTF(("supR3HardenedScreenImage/%s: %d (was %d) fWinVerifyTrust=%d for '%ls'\n",
pCacheHit->fWinVerifyTrust = true;
}
else
SUP_DPRINTF(("supR3HardenedScreenImage/%s: WinVerifyTrust not available, rescheduling %ls\n",
}
else
SUP_DPRINTF(("supR3HardenedScreenImage/%s: cache hit (%Rrc) on %ls [avoiding WinVerifyTrust]\n",
}
SUP_DPRINTF(("supR3HardenedScreenImage/%s: cache hit (%Rrc) on %ls%s\n",
pszCaller, pCacheHit->rc, pCacheHit->wszPath, pCacheHit->fWinVerifyTrust ? "" : " [lacks WinVerifyTrust]"));
/* Return the cached value. */
{
*pfCallRealApi = true;
return STATUS_SUCCESS;
}
if ( cErrorHits < 8
supR3HardenedError(VINF_SUCCESS, false,
"supR3HardenedScreenImage/%s: cached rc=%Rrc fImage=%d fProtect=%#x fAccess=%#x cErrorHits=%u %ls\n",
else if (pfQuietFailure)
*pfQuietFailure = true;
return STATUS_TRUST_FAILURE;
}
/*
* On XP the loader might hand us handles with just FILE_EXECUTE and
* SYNCHRONIZE, the means reading will fail later on. Also, we need
* READ_CONTROL access to check the file ownership later on, and non
* of the OS versions seems be giving us that. So, in effect we
* more or less always reopen the file here.
*/
&hMyFile,
0 /* Handle attributes*/, 0 /* Options */);
if (!NT_SUCCESS(rcNt))
{
if (rcNt == STATUS_ACCESS_DENIED)
{
InitializeObjectAttributes(&ObjAttr, &uBuf.UniStr, OBJ_CASE_INSENSITIVE, NULL /*hRootDir*/, NULL /*pSecDesc*/);
&ObjAttr,
&Ios,
NULL /* Allocation Size*/,
NULL /*EaBuffer*/,
0 /*EaLength*/);
if (NT_SUCCESS(rcNt))
if (!NT_SUCCESS(rcNt))
{
supR3HardenedError(VINF_SUCCESS, false,
"supR3HardenedScreenImage/%s: Failed to duplicate and open the file: rcNt=%#x hFile=%p %ls\n",
return rcNt;
}
/* Check that we've got the same file. */
if ( fMyValid
{
supR3HardenedError(VINF_SUCCESS, false,
"supR3HardenedScreenImage/%s: Re-opened has different ID that input: %#llx vx %#llx (%ls)\n",
return STATUS_TRUST_FAILURE;
}
}
else
{
#ifdef DEBUG
supR3HardenedError(VINF_SUCCESS, false,
#endif
}
}
/*
* Special Kludge for Windows XP and W2K3 and their stupid attempts
* at mapping a hidden XML file called c:\Windows\WindowsShell.Manifest
* with executable access. The image bit isn't set, fortunately.
*/
if ( !fImage
{
PRTUTF16 pwszName = &uBuf.UniStr.Buffer[(g_System32NtPath.UniStr.Length - sizeof(L"System32") + sizeof(WCHAR)) / sizeof(WCHAR)];
{
/*
* Drop all executable access to the mapping and let it continue.
*/
SUP_DPRINTF(("supR3HardenedScreenImage/%s: Applying the drop-exec-kludge for '%ls'\n", pszCaller, uBuf.UniStr.Buffer));
if (*pfAccess & SECTION_MAP_EXECUTE)
if (*pfProtect & PAGE_EXECUTE)
*pfCallRealApi = true;
return STATUS_SUCCESS;
}
}
#ifndef VBOX_PERMIT_EVEN_MORE
/*
* Check the path. We don't allow DLLs to be loaded from just anywhere:
* 1. System32 - normal code or cat signing, owner TrustedInstaller.
* 2. WinSxS - normal code or cat signing, owner TrustedInstaller.
* 3. VirtualBox - kernel code signing and integrity checks.
* 4. AppPatchDir - normal code or cat signing, owner TrustedInstaller.
* 5. Program Files - normal code or cat signing, owner TrustedInstaller.
* 6. Common Files - normal code or cat signing, owner TrustedInstaller.
* 7. x86 variations of 4 & 5 - ditto.
*/
if (supHardViUniStrPathStartsWithUniStr(&uBuf.UniStr, &g_System32NtPath.UniStr, true /*fCheckSlash*/))
else if (supHardViUniStrPathStartsWithUniStr(&uBuf.UniStr, &g_WinSxSNtPath.UniStr, true /*fCheckSlash*/))
g_offSupLibHardenedExeNtName, false /*fCheckSlash*/))
# ifdef VBOX_PERMIT_MORE
else if (supHardViUniStrPathStartsWithUniStr(&uBuf.UniStr, &g_ProgramFilesNtPath.UniStr, true /*fCheckSlash*/))
else if (supHardViUniStrPathStartsWithUniStr(&uBuf.UniStr, &g_CommonFilesNtPath.UniStr, true /*fCheckSlash*/))
# ifdef RT_ARCH_AMD64
else if (supHardViUniStrPathStartsWithUniStr(&uBuf.UniStr, &g_ProgramFilesX86NtPath.UniStr, true /*fCheckSlash*/))
else if (supHardViUniStrPathStartsWithUniStr(&uBuf.UniStr, &g_CommonFilesX86NtPath.UniStr, true /*fCheckSlash*/))
# endif
# endif
/* Hack to allow profiling our code with Visual Studio. */
&& memcmp(uBuf.UniStr.Buffer + (uBuf.UniStr.Length - sizeof(L"\\SamplingRuntime.dll") + sizeof(WCHAR)) / sizeof(WCHAR),
{
*pfCallRealApi = true;
return STATUS_SUCCESS;
}
# endif
else
{
supR3HardenedError(VINF_SUCCESS, false,
"supR3HardenedScreenImage/%s: Not a trusted location: '%ls' (fImage=%d fProtect=%#x fAccess=%#x)\n",
return STATUS_TRUST_FAILURE;
}
#else /* VBOX_PERMIT_EVEN_MORE */
/*
* Require trusted installer + some kind of signature on everything, except
* for the VBox bits where we require kernel code signing and special
* integrity checks.
*/
g_offSupLibHardenedExeNtName, false /*fCheckSlash*/))
else
#endif /* VBOX_PERMIT_EVEN_MORE */
/*
* Do the verification. For better error message we borrow what's
* left of the path buffer for an RTERRINFO buffer.
*/
int rc;
bool fWinVerifyTrust = false;
rc = supHardenedWinVerifyImageByHandle(hMyFile, uBuf.UniStr.Buffer, fFlags, fAvoidWinVerifyTrust, &fWinVerifyTrust, &ErrInfo);
if (RT_FAILURE(rc))
{
supR3HardenedError(VINF_SUCCESS, false,
"supR3HardenedScreenImage/%s: rc=%Rrc fImage=%d fProtect=%#x fAccess=%#x %ls: %s\n",
return STATUS_TRUST_FAILURE;
}
/*
* Insert into the cache.
*/
*pfCallRealApi = true;
return STATUS_SUCCESS;
}
/**
* Preloads a file into the verify cache if possible.
*
* This is used to avoid known cyclic LoadLibrary issues with WinVerifyTrust.
*
* @param pwszName The name of the DLL to verify.
*/
{
InitializeObjectAttributes(&ObjAttr, &UniStr, OBJ_CASE_INSENSITIVE, NULL /*hRootDir*/, NULL /*pSecDesc*/);
&ObjAttr,
&Ios,
NULL /* Allocation Size*/,
NULL /*EaBuffer*/,
0 /*EaLength*/);
if (NT_SUCCESS(rcNt))
if (!NT_SUCCESS(rcNt))
{
return;
}
bool fCallRealApi;
//SUP_DPRINTF(("supR3HardenedWinVerifyCachePreload: scanning %ls\n", pwszName));
supR3HardenedScreenImage(hFile, false, &fAccess, &fProtect, &fCallRealApi, "preload", false /*fAvoidWinVerifyTrust*/,
NULL /*pfQuietFailure*/);
//SUP_DPRINTF(("supR3HardenedWinVerifyCachePreload: done %ls\n", pwszName));
}
/**
* Hook that monitors NtCreateSection calls.
*
* @returns NT status code.
* @param phSection Where to return the section handle.
* @param fAccess The desired access.
* @param pObjAttribs The object attributes (optional).
* @param pcbSection The section size (optional).
* @param fProtect The max section protection.
* @param fAttribs The section attributes.
* @param hFile The file to create a section from (optional).
*/
supR3HardenedMonitor_NtCreateSection(PHANDLE phSection, ACCESS_MASK fAccess, POBJECT_ATTRIBUTES pObjAttribs,
{
&& hFile != INVALID_HANDLE_VALUE)
{
bool const fExecProt = RT_BOOL(fProtect & (PAGE_EXECUTE | PAGE_EXECUTE_READ | PAGE_EXECUTE_WRITECOPY
{
bool fCallRealApi;
//SUP_DPRINTF(("supR3HardenedMonitor_NtCreateSection: 1\n"));
//SUP_DPRINTF(("supR3HardenedMonitor_NtCreateSection: 2 rcNt=%#x fCallRealApi=%#x\n", rcNt, fCallRealApi));
if (!NT_SUCCESS(rcNt))
return rcNt;
if (!fCallRealApi)
return STATUS_TRUST_FAILURE;
}
}
/*
* Call checked out OK, call the original.
*/
return g_pfnNtCreateSectionReal(phSection, fAccess, pObjAttribs, pcbSection, fProtect, fAttribs, hFile);
}
/**
* Hooks that intercepts LdrLoadDll calls.
*
* Two purposes:
* -# Enforce our own search path restrictions.
* -# Prevalidate DLLs about to be loaded so we don't upset the loader data
* by doing it from within the NtCreateSection hook (WinVerifyTrust
* seems to be doing harm there on W7/32).
*
* @returns
* @param pwszSearchPath The search path to use.
* @param pfFlags Flags on input. DLL characteristics or something
* on return?
* @param pName The name of the module.
* @param phMod Where the handle of the loaded DLL is to be
* returned to the caller.
*/
supR3HardenedMonitor_LdrLoadDll(PWSTR pwszSearchPath, PULONG pfFlags, PUNICODE_STRING pName, PHANDLE phMod)
{
/*
* Process WinVerifyTrust todo before and after.
*/
/*
* Reject things we don't want to deal with.
*/
{
supR3HardenedError(VINF_SUCCESS, false, "supR3HardenedMonitor_LdrLoadDll: name is NULL or have a zero length.\n");
return STATUS_INVALID_PARAMETER;
}
SUP_DPRINTF(("supR3HardenedMonitor_LdrLoadDll: pName=%.*ls *pfFlags=%#x pwszSearchPath=%p:%ls\n",
(unsigned)pName->Length / sizeof(WCHAR), pName->Buffer, pfFlags ? *pfFlags : UINT32_MAX, pwszSearchPath,
!((uintptr_t)pwszSearchPath & 1) && (uintptr_t)pwszSearchPath >= 0x2000U ? pwszSearchPath : L"<flags>"));
/*
* Reject long paths that's close to the 260 limit without looking.
*/
{
supR3HardenedError(VINF_SUCCESS, false, "supR3HardenedMonitor_LdrLoadDll: too long name: %#x bytes\n", pName->Length);
return STATUS_NAME_TOO_LONG;
}
/*
* Absolute path?
*/
bool fSkipValidation = false;
)
{
}
/*
* Not an absolute path. Check if it's one of those special API set DLLs
* or something we're known to use but should be taken from WinSxS.
*/
L"api-ms-win-", 11, false /*fCheckSlash*/))
{
fSkipValidation = true;
}
/*
* Not an absolute path or special API set. There are two alternatives
* now, either there is no path at all or there is a relative path. We
* will resolve it to an absolute path in either case, failing the call
* if we can't.
*/
else
{
switch (pawcName[i])
{
case '\\':
case '/':
offLastSlash = i;
break;
case '.':
offLastDot = i;
break;
}
cwcName--;
/*
* Reject relative paths for now as they might be breakout attempts.
*/
if (offLastSlash != UINT32_MAX)
{
supR3HardenedError(VINF_SUCCESS, false,
"supR3HardenedMonitor_LdrLoadDll: relative name not permitted: %.*ls\n",
return STATUS_OBJECT_NAME_INVALID;
}
/*
* Perform dll redirection to WinSxS such. We using an undocumented
* API here, which as always is a bit risky... ASSUMES that the API
* returns a full DOS path.
*/
NULL /*pNewFlags*/,
NULL /*pcbFilename*/,
NULL /*pcbNeeded*/);
if (NT_SUCCESS(rcNt))
{
if (pUniStrResult != &UniStrDynamic)
else
{
{
supR3HardenedError(VINF_SUCCESS, false,
"supR3HardenedMonitor_LdrLoadDll: Name too long: %.*ls -> %.*ls (RtlDosApplyFileIoslationRedirection_Ustr)\n",
return STATUS_NAME_TOO_LONG;
}
}
}
else
{
/*
* Search for the DLL. Only System32 is allowed as the target of
* a search on the API level, all VBox calls will have full paths.
*/
if (!cwc)
{
supR3HardenedError(VINF_SUCCESS, false,
"supR3HardenedMonitor_LdrLoadDll: GetSystemDirectoryW failed: %u\n", GetLastError());
return STATUS_UNEXPECTED_IO_ERROR;
}
{
supR3HardenedError(VINF_SUCCESS, false,
return STATUS_NAME_TOO_LONG;
}
if (!fNeedDllSuffix)
else
{
cwc += 4;
}
}
SUP_DPRINTF(("supR3HardenedMonitor_LdrLoadDll: '%.*ls' -> '%.*ls' [rcNt=%#x]\n",
pName = &ResolvedName;
}
if (!fSkipValidation)
{
/*
* Try open the file. If this fails, never mind, just pass it on to
* the real API as we've replaced any searchable name with a full name
* and the real API can come up with a fitting status code for it.
*/
if (hFile != INVALID_HANDLE_VALUE)
{
bool fCallRealApi = false;
bool fQuietFailure = false;
if (!NT_SUCCESS(rcNt))
{
if (!fQuietFailure)
{
supR3HardenedError(VINF_SUCCESS, false, "supR3HardenedMonitor_LdrLoadDll: rejecting '%ls': rcNt=%#x\n",
}
return rcNt;
}
}
else
{
}
}
/*
* Screened successfully enough. Call the real thing.
*/
/*
* Log the result and process pending WinVerifyTrust work if we can.
*/
SUP_DPRINTF(("supR3HardenedMonitor_LdrLoadDll: returns rcNt=%#x hMod=%p '%ls'\n", rcNt, *phMod, wszPath));
else
return rcNt;
}
#ifdef RT_ARCH_AMD64
/**
* Tries to allocate memory between @a uStart and @a uEnd.
*
* @returns Pointer to the memory on success. NULL on failure.
* @param uStart The start address.
* @param uEnd The end address. This is lower than @a uStart
* if @a iDirection is negative, and higher if
* positive.
* @param iDirection The search direction.
* @param cbAlloc The number of bytes to allocate.
*/
static void *supR3HardenedWinAllocHookMemory(uintptr_t uStart, uintptr_t uEnd, intptr_t iDirection, size_t cbAlloc)
{
/*
* Make uEnd the last valid return address.
*/
if (iDirection > 0)
{
}
else
/*
* Search for free memory.
*/
for (;;)
{
/*
* Examine the memory at this address, if it's free, try make the allocation here.
*/
(void *)uCur,
&MemInfo,
sizeof(MemInfo),
&cbIgn));
{
for (;;)
{
if (NT_SUCCESS(rcNt))
{
if ( iDirection > 0
return pvMem;
}
/* Advance within the free area and try again? */
if ( iDirection > 0
break;
}
}
/*
* Advance to the next memory region.
*/
if (iDirection > 0)
{
break;
}
else
{
break;
}
}
return NULL;
}
#endif
{
"Failed to install %s monitor: %x %x %x %x %x %x %x %x %x %x %x %x %x %x %x %x\n "
#ifdef RT_ARCH_X86
"(It is also possible you are running 32-bit VirtualBox under 64-bit windows.)\n"
#endif
,
}
/**
* IPRT thread that waits for the parent process to terminate and reacts by
* exiting the current process.
*
* @returns VINF_SUCCESS
* @param hSelf The current thread. Ignored.
* @param pvUser The handle of the parent process.
*/
{
/*
* Wait for the parent to terminate.
*/
for (;;)
{
if ( rcNt == STATUS_WAIT_0
|| rcNt == STATUS_ABANDONED_WAIT_0)
break;
if ( rcNt != STATUS_TIMEOUT
&& rcNt != STATUS_USER_APC
&& rcNt != STATUS_ALERTED)
}
/*
* Proxy the termination code of the child, if it exited already.
*/
NTSTATUS rcNt2 = NtQueryInformationProcess(hProcWait, ProcessBasicInformation, &BasicInfo, sizeof(BasicInfo), NULL);
if ( !NT_SUCCESS(rcNt2)
SUP_DPRINTF(("supR3HardenedWinParentWatcherThread: Quitting: ExitCode=%#x rcNt=%#x\n", BasicInfo.ExitStatus, rcNt));
return VINF_SUCCESS; /* won't be reached. */
}
/**
* Creates the parent watcher thread that will make sure this process exits when
* the parent does.
*
* This is a necessary evil to make VBoxNetDhcp and VBoxNetNat termination from
* Main work without too much new magic. It also makes Ctrl-C or similar work
* in on the hardened processes in the windows console.
*
* @param hVBoxRT The VBoxRT.dll handle. We use RTThreadCreate to
* spawn the thread to avoid duplicating thread
* creation and thread naming code from IPRT.
*/
{
/*
* Resolve runtime methods that we need.
*/
/*
* Find the parent process ID.
*/
NTSTATUS rcNt = NtQueryInformationProcess(NtCurrentProcess(), ProcessBasicInformation, &BasicInfo, sizeof(BasicInfo), NULL);
if (!NT_SUCCESS(rcNt))
supR3HardenedFatal("supR3HardenedWinCreateParentWatcherThread: NtQueryInformationProcess failed: %#x\n", rcNt);
/*
* Open the parent process for waiting and exitcode query.
*/
if (!NT_SUCCESS(rcNt))
supR3HardenedFatalMsg("supR3HardenedWinCreateParentWatcherThread", kSupInitOp_Misc, VERR_GENERAL_FAILURE,
/*
* Create the thread that should do the waiting.
*/
if (RT_FAILURE(rc))
}
/**
* Install hooks for intercepting calls dealing with mapping shared libraries
* into the process.
*
* This allows us to prevent undesirable shared libraries from being loaded.
*
* @remarks We assume we're alone in this process, so no seralizing trickery is
* necessary when installing the patch.
*
* @remarks We would normally just copy the prologue sequence somewhere and add
* a jump back at the end of it. But because we wish to avoid
* allocating executable memory, we need to have preprepared assembly
* "copies". This makes the non-system call patching a little tedious
* and inflexible.
*/
DECLHIDDEN(void) supR3HardenedWinInstallHooks(void)
{
#ifndef VBOX_WITHOUT_DEBUGGER_CHECKS
/*
* Install a anti debugging hack before we continue. This prevents most
* notifications from ending up in the debugger. (Also applied to the
* child process when respawning.)
*/
if (!NT_SUCCESS(rcNt))
"NtSetInformationThread/ThreadHideFromDebugger failed: %#x\n", rcNt);
#endif
/*
* Disable hard error popups so we can quietly refuse images to be loaded.
*/
rcNt = NtQueryInformationProcess(NtCurrentProcess(), ProcessDefaultHardErrorMode, &fHardErr, sizeof(fHardErr), NULL);
if (!NT_SUCCESS(rcNt))
"NtQueryInformationProcess/ProcessDefaultHardErrorMode failed: %#x\n", rcNt);
{
rcNt = NtSetInformationProcess(NtCurrentProcess(), ProcessDefaultHardErrorMode, &fHardErr, sizeof(fHardErr));
if (!NT_SUCCESS(rcNt))
"NtSetInformationProcess/ProcessDefaultHardErrorMode failed: %#x\n", rcNt);
}
/*
* Locate the routines first so we can allocate memory that's near enough.
*/
//SUPR3HARDENED_ASSERT(pfnNtCreateSection == (FARPROC)NtCreateSection);
//SUPR3HARDENED_ASSERT(pfnLdrLoadDll == (FARPROC)LdrLoadDll);
#ifdef RT_ARCH_AMD64
/*
* For 64-bit hosts we need some memory within a +/-2GB range of the
* actual function to be able to patch it.
*/
if (!pvMem)
{
if (!pvMem)
"Failed to allocate memory within the +/-2GB range from NTDLL.\n");
}
#endif
/*
* Hook #1 - NtCreateSection.
* Purpose: Validate everything that can be mapped into the process before
* it's mapped and we still have a file handle to work with.
*/
#ifdef RT_ARCH_AMD64
/*
* Patch 64-bit hosts.
*/
0:000> u ntdll!NtCreateSection
ntdll!NtCreateSection:
00000000`779f1750 4c8bd1 mov r10,rcx
00000000`779f1753 b847000000 mov eax,47h
00000000`779f1758 0f05 syscall
00000000`779f175a c3 ret
00000000`779f175b 0f1f440000 nop dword ptr [rax+rax]
The variant is the value loaded into eax: W2K3=??, Vista=47h?, W7=47h, W80=48h, W81=49h */
/* b8 22 35 ed 0 48 63 c0 ff e0 c3 f 1f 44 0 0 - necros2 - agnitum firewall? */
)
{
switch (pbNtCreateSection[4])
{
# define SYSCALL(a_Num) case a_Num: pfnCallReal = RT_CONCAT(supR3HardenedJmpBack_NtCreateSection_,a_Num); break;
# include "NtCreateSection-template-amd64-syscall-type-1.h"
}
}
if (!pfnCallReal)
pbNtCreateSection[0] = 0xff;
*(uint32_t *)&pbNtCreateSection[2] = (uint32_t)((uintptr_t)puJmpTab - (uintptr_t)&pbNtCreateSection[2+4]);
puJmpTab++;
#else
/*
* Patch 32-bit hosts.
*/
/* Pattern #1: XP thru Windows 7
kd> u ntdll!NtCreateSection
ntdll!NtCreateSection:
7c90d160 b832000000 mov eax,32h
7c90d165 ba0003fe7f mov edx,offset SharedUserData!SystemCallStub (7ffe0300)
7c90d16a ff12 call dword ptr [edx]
7c90d16c c21c00 ret 1Ch
7c90d16f 90 nop
The variable bit is the value loaded into eax: XP=32h, W2K3=34h, Vista=4bh, W7=54h
Pattern #2: Windows 8.1
0:000:x86> u ntdll_6a0f0000!NtCreateSection
ntdll_6a0f0000!NtCreateSection:
6a15eabc b854010000 mov eax,154h
6a15eac1 e803000000 call ntdll_6a0f0000!NtCreateSection+0xd (6a15eac9)
6a15eac6 c21c00 ret 1Ch
6a15eac9 8bd4 mov edx,esp
6a15eacb 0f34 sysenter
6a15eacd c3 ret
The variable bit is the value loaded into eax: W81=154h
Note! One nice thing here is that we can share code pattern #1. */
)
)
{
{
# define SYSCALL(a_Num) case a_Num: pfnCallReal = RT_CONCAT(supR3HardenedJmpBack_NtCreateSection_,a_Num); break;
# include "NtCreateSection-template-x86-syscall-type-1.h"
}
}
if (!pfnCallReal)
pbNtCreateSection[0] = 0xe9;
#endif
/*
* Hook #2 - LdrLoadDll
* Purpose: (a) Enforce LdrLoadDll search path constraints, and (b) pre-validate
* DLLs so we can avoid calling WinVerifyTrust from the first hook,
* and thus avoiding messing up the loader data on some installations.
*
* This differs from the above function in that is no a system call and
* we're at the mercy of the compiler.
*/
uint32_t offExecPage = 0;
#ifdef RT_ARCH_AMD64
/*
* Patch 64-bit hosts.
*/
# if 0
/* Pattern #1:
Windows 8.1:
0:000> u ntdll!LdrLoadDll
ntdll!LdrLoadDll:
00007ffa`814ccd44 488bc4 mov rax,rsp
00007ffa`814ccd47 48895808 mov qword ptr [rax+8],rbx
00007ffa`814ccd4b 48896810 mov qword ptr [rax+10h],rbp
00007ffa`814ccd4f 48897018 mov qword ptr [rax+18h],rsi
00007ffa`814ccd53 48897820 mov qword ptr [rax+20h],rdi
00007ffa`814ccd57 4156 push r14
00007ffa`814ccd59 4883ec70 sub rsp,70h
00007ffa`814ccd5d f6059cd2100009 test byte ptr [ntdll!LdrpDebugFlags (00007ffa`815da000)],9
*/
{
}
/*
Pattern #2:
Windows 8.0:
0:000> u ntdll_w8_64!LdrLoadDll
ntdll_w8_64!LdrLoadDll:
00007ffa`52ffa7c0 48895c2408 mov qword ptr [rsp+8],rbx
00007ffa`52ffa7c5 4889742410 mov qword ptr [rsp+10h],rsi
00007ffa`52ffa7ca 48897c2418 mov qword ptr [rsp+18h],rdi
00007ffa`52ffa7cf 55 push rbp
00007ffa`52ffa7d0 4156 push r14
00007ffa`52ffa7d2 4157 push r15
00007ffa`52ffa7d4 488bec mov rbp,rsp
00007ffa`52ffa7d7 4883ec60 sub rsp,60h
00007ffa`52ffa7db 8b05df321000 mov eax,dword ptr [ntdll_w8_64!LdrpDebugFlags (00007ffa`530fdac0)]
00007ffa`52ffa7e1 4d8bf1 mov r14,r9
*/
{
}
/*
Pattern #3:
Windows 7:
ntdll_w7_64!LdrLoadDll:
00000000`58be4a20 48895c2410 mov qword ptr [rsp+10h],rbx
00000000`58be4a25 48896c2418 mov qword ptr [rsp+18h],rbp
00000000`58be4a2a 56 push rsi
00000000`58be4a2b 57 push rdi
00000000`58be4a2c 4154 push r12
00000000`58be4a2e 4883ec50 sub rsp,50h
00000000`58be4a32 f605976e100009 test byte ptr [ntdll_w7_64!ShowSnaps (00000000`58ceb8d0)],9
00000000`58be4a39 498bf1 mov rsi,r9
*/
{
}
/*
Pattern #4:
Windows Vista:
0:000> u ntdll_vista_64!LdrLoadDll
ntdll_vista_64!LdrLoadDll:
00000000`58c11f60 fff3 push rbx
00000000`58c11f62 56 push rsi
00000000`58c11f63 57 push rdi
00000000`58c11f64 4154 push r12
00000000`58c11f66 4155 push r13
00000000`58c11f68 4156 push r14
00000000`58c11f6a 4157 push r15
00000000`58c11f6c 4881ecb0020000 sub rsp,2B0h
00000000`58c11f73 488b05367b0e00 mov rax,qword ptr [ntdll_vista_64!_security_cookie (00000000`58cf9ab0)]
00000000`58c11f7a 4833c4 xor rax,rsp
00000000`58c11f7d 48898424a0020000 mov qword ptr [rsp+2A0h],rax
*/
{
}
/*
Pattern #5:
Windows XP64:
0:000> u ntdll!LdrLoadDll
ntdll!LdrLoadDll:
00000000`78efa580 4c8bdc mov r11,rsp
00000000`78efa583 4881ece8020000 sub rsp,2E8h
00000000`78efa58a 49895bf8 mov qword ptr [r11-8],rbx
00000000`78efa58e 498973f0 mov qword ptr [r11-10h],rsi
00000000`78efa592 49897be8 mov qword ptr [r11-18h],rdi
00000000`78efa596 4d8963e0 mov qword ptr [r11-20h],r12
00000000`78efa59a 4d896bd8 mov qword ptr [r11-28h],r13
00000000`78efa59e 4d8973d0 mov qword ptr [r11-30h],r14
00000000`78efa5a2 4d897bc8 mov qword ptr [r11-38h],r15
00000000`78efa5a6 488b051bd10a00 mov rax,qword ptr [ntdll!_security_cookie (00000000`78fa76c8)]
00000000`78efa5ad 48898424a0020000 mov qword ptr [rsp+2A0h],rax
00000000`78efa5b5 4d8bf9 mov r15,r9
00000000`78efa5b8 4c8bf2 mov r14,rdx
00000000`78efa5bb 4c8be9 mov r13,rcx
00000000`78efa5be 4c89442458 mov qword ptr [rsp+58h],r8
00000000`78efa5c3 66c74424680000 mov word ptr [rsp+68h],0
*/
{
}
else
# else
/* Just use the disassembler to skip 6 bytes or more. */
offJmpBack = 0;
while (offJmpBack < 6)
{
cbInstr = 1;
if ( RT_FAILURE(rc)
offJmpBack += cbInstr;
}
# endif
/* Assemble the code for resuming the call.*/
*(uint32_t *)&g_abSupHardReadWriteExecPage[offExecPage] = RT_ALIGN_32(offExecPage + 4, 8) - (offExecPage + 4);
/* Patch the function. */
SUPR3HARDENED_ASSERT_WIN32_SUCCESS(VirtualProtectEx(NtCurrentProcess(), pbLdrLoadDll, 16, PAGE_EXECUTE_READWRITE, &dwOldProt));
pbLdrLoadDll[0] = 0xff;
SUPR3HARDENED_ASSERT_WIN32_SUCCESS(VirtualProtectEx(NtCurrentProcess(), pbLdrLoadDll, 16, PAGE_EXECUTE_READ, &dwOldProt));
puJmpTab++;
#else
/*
* Patch 32-bit hosts.
*/
# if 0
/* Pattern #1:
Windows 7:
0:000> u ntdll!LdrLoadDll
ntdll!LdrLoadDll:
77aff585 8bff mov edi,edi
77aff587 55 push ebp
77aff588 8bec mov ebp,esp
77aff58a 51 push ecx
77aff58b 51 push ecx
77aff58c a1f8bdaf77 mov eax,dword ptr [ntdll!LdrpLogLevelStateTable+0x24 (77afbdf8)]
Windows 8 rtm:
0:000:x86> u ntdll_67150000!LdrLoadDll
ntdll_67150000!LdrLoadDll:
67189f3f 8bff mov edi,edi
67189f41 55 push ebp
67189f42 8bec mov ebp,esp
67189f44 8b0d10eb2467 mov ecx,dword ptr [ntdll_67150000!LdrpDebugFlags (6724eb10)]
Windows 8.1:
0:000:x86> u ntdll_w81_32!LdrLoadDll
ntdll_w81_32!LdrLoadDll:
6718aade 8bff mov edi,edi
6718aae0 55 push ebp
6718aae1 8bec mov ebp,esp
6718aae3 83ec14 sub esp,14h
6718aae6 f6050040246709 test byte ptr [ntdll_w81_32!LdrpDebugFlags (67244000)],9
Pattern #2:
Windows XP:
0:000:x86> u ntdll_xp!LdrLoadDll
ntdll_xp!LdrLoadDll:
77f569d2 6858020000 push 258h
77f569d7 68d866f777 push offset ntdll_xp!`string'+0x12c (77f766d8)
77f569dc e83bb20200 call ntdll_xp!_SEH_prolog (77f81c1c)
77f569e1 33db xor ebx,ebx
77f569e3 66895de0 mov word ptr [ebp-20h],bx
77f569e7 33c0 xor eax,eax
77f569e9 8d7de2 lea edi,[ebp-1Eh]
77f569ec ab stos dword ptr es:[edi]
Windows Server 2003:
0:000:x86> u ntdll_w2k3_32!LdrLoadDll
ntdll_w2k3_32!LdrLoadDll:
7c833f63 6840020000 push 240h
7c833f68 68b040837c push offset ntdll_w2k3_32!`string'+0x12c (7c8340b0)
7c833f6d e8a942ffff call ntdll_w2k3_32!_SEH_prolog (7c82821b)
7c833f72 a13077887c mov eax,dword ptr [ntdll_w2k3_32!__security_cookie (7c887730)]
7c833f77 8945e4 mov dword ptr [ebp-1Ch],eax
7c833f7a 8b4508 mov eax,dword ptr [ebp+8]
7c833f7d 8985b0fdffff mov dword ptr [ebp-250h],eax
7c833f83 8b450c mov eax,dword ptr [ebp+0Ch]
Windows Vista SP0 & SP1:
0:000:x86> u ntdll_vista_sp0_32!LdrLoadDll
ntdll_vista_sp0_32!LdrLoadDll:
69b0eb00 6844020000 push 244h
69b0eb05 6838e9b269 push offset ntdll_vista_sp0_32! ?? ::FNODOBFM::`string'+0x39e (69b2e938)
69b0eb0a e835420300 call ntdll_vista_sp0_32!_SEH_prolog4_GS (69b42d44)
69b0eb0f 8b4508 mov eax,dword ptr [ebp+8]
69b0eb12 8985acfdffff mov dword ptr [ebp-254h],eax
69b0eb18 8b450c mov eax,dword ptr [ebp+0Ch]
69b0eb1b 8985c0fdffff mov dword ptr [ebp-240h],eax
69b0eb21 8b4510 mov eax,dword ptr [ebp+10h]
*/
{
}
{
offJmpBack = 5;
}
else
# else
/* Just use the disassembler to skip 6 bytes or more. */
offJmpBack = 0;
while (offJmpBack < 5)
{
cbInstr = 1;
if ( RT_FAILURE(rc)
offJmpBack += cbInstr;
}
/* Assemble the code for resuming the call.*/
# endif
/* Patch LdrLoadDLl. */
pbLdrLoadDll[0] = 0xe9;
*(uint32_t *)&pbLdrLoadDll[1] = (uintptr_t)supR3HardenedMonitor_LdrLoadDll - (uintptr_t)&pbLdrLoadDll[1+4];
SUPR3HARDENED_ASSERT_WIN32_SUCCESS(VirtualProtectEx(NtCurrentProcess(), pbLdrLoadDll, 16, PAGE_EXECUTE_READ, &dwOldProt));
#endif
/*
* Seal the rwx page.
*/
SUPR3HARDENED_ASSERT_WIN32_SUCCESS(VirtualProtectEx(NtCurrentProcess(), g_abSupHardReadWriteExecPage, PAGE_SIZE,
}
/**
* Verifies the process integrity.
*/
DECLHIDDEN(void) supR3HardenedWinVerifyProcess(void)
{
if (RT_FAILURE(rc))
}
/**
* Gets the SID of the user associated with the process.
*
* @returns @c true if we've got a login SID, @c false if not.
* @param pSidUser Where to return the user SID.
* @param cbSidUser The size of the user SID buffer.
* @param pSidLogin Where to return the login SID.
* @param cbSidLogin The size of the login SID buffer.
*/
static bool supR3HardenedGetUserAndLogSids(PSID pSidUser, ULONG cbSidUser, PSID pSidLogin, ULONG cbSidLogin)
{
union
{
} uBuf;
SUPR3HARDENED_ASSERT_NT_SUCCESS(NtQueryInformationToken(hToken, TokenUser, &uBuf, sizeof(uBuf), &cbRet));
bool fLoginSid = false;
if (NT_SUCCESS(rcNt))
{
{
fLoginSid = true;
break;
}
}
return fLoginSid;
}
/**
* Build security attributes for the process or the primary thread (@a fProcess)
*
* Process DACLs can be bypassed using the SeDebugPrivilege (generally available
* modifying the DACL. However, it restricts
*
* @param pSecAttrs Where to return the security attributes.
* @param pCleanup Cleanup record.
* @param fProcess Set if it's for the process, clear if it's for
* the primary thread.
*/
static void supR3HardenedInitSecAttrs(PSECURITY_ATTRIBUTES pSecAttrs, PMYSECURITYCLEANUP pCleanup, bool fProcess)
{
/*
* Safe return values.
*/
/** @todo This isn't at all complete, just sketches... */
/*
* Create an ACL detailing the access of the above groups.
*/
SUPR3HARDENED_ASSERT_NT_SUCCESS(RtlCreateAcl(&pCleanup->Acl.AclHdr, sizeof(pCleanup->Acl), ACL_REVISION));
if (fProcess)
{
{
}
}
else
{
fDeny |= THREAD_SUSPEND_RESUME | THREAD_SET_CONTEXT | THREAD_SET_INFORMATION | THREAD_SET_THREAD_TOKEN
{
}
}
/* Deny everyone access to bad bits. */
#if 1
#endif
#if 0
/* Grant some access to the owner - doesn't work. */
#endif
#if 1
# if 1
/* Grant minimal access to the user. */
# endif
# if 1
/* Grant very limited access to the login sid. */
if (fHasLoginSid)
{
}
# endif
#endif
/*
* Create a security descriptor with the above ACL.
*/
PSECURITY_DESCRIPTOR pSecDesc = (PSECURITY_DESCRIPTOR)suplibHardenedAllocZ(SECURITY_DESCRIPTOR_MIN_LENGTH);
SUPR3HARDENED_ASSERT_NT_SUCCESS(RtlCreateSecurityDescriptor(pSecDesc, SECURITY_DESCRIPTOR_REVISION));
SUPR3HARDENED_ASSERT_NT_SUCCESS(RtlSetDaclSecurityDescriptor(pSecDesc, TRUE /*fDaclPresent*/, &pCleanup->Acl.AclHdr,
FALSE /*fDaclDefaulted*/));
}
/**
* Predicate function which tests whether @a ch is a argument separator
* character.
*
* @param ch The character to examine.
*/
{
return ch == ' '
|| ch == '\t'
|| ch == '\n'
|| ch == '\r';
}
/**
* Construct the new command line.
*
* suplibHardenedWindowsMain), we skip the argument by argument UTF-8 -> UTF-16
* conversion and quoting by going to the original source.
*
* The executable name, though, is replaced in case it's not a fullly
* qualified path.
*
* The re-spawn indicator is added immediately after the executable name
* so that we don't get tripped up missing close quote chars in the last
* argument.
*
* @returns Pointer to a command line string (heap).
* @param pUniStr Unicode string structure to initialize to the
* command line. Optional.
* @param iWhich Which respawn we're to check for, 1 being the first
* one, and 2 the second and final.
*/
{
/*
* Get the command line and skip the executable name.
*/
/* Skip leading space (shouldn't be any, but whatever). */
/* Walk to the end of it. */
int fQuoted = false;
do
{
if (*pawcArgs == '"')
{
}
else
{
unsigned cSlashes = 0;
do
{
cSlashes++;
cwcArgs--;
pawcArgs++;
}
}
/* Skip trailing spaces. */
/*
* Allocate a new buffer.
*/
size_t cwcCmdLine = (sizeof(SUPR3_RESPAWN_1_ARG0) - 1) / sizeof(SUPR3_RESPAWN_1_ARG0[0]) /* Respawn exe name. */
"Command line is too long (%u chars)!", cwcCmdLine);
PRTUTF16 pwszCmdLine = (PRTUTF16)HeapAlloc(GetProcessHeap(), 0 /* dwFlags*/, (cwcCmdLine + 1) * sizeof(RTUTF16));
/*
* Construct the new command line.
*/
for (const char *pszSrc = iWhich == 1 ? SUPR3_RESPAWN_1_ARG0 : SUPR3_RESPAWN_2_ARG0; *pszSrc; pszSrc++)
if (cwcArgs)
{
*pwszDst++ = ' ';
}
*pwszDst = '\0';
if (pString)
{
}
return pwszCmdLine;
}
/**
* Check if the zero terminated NT unicode string is the path to the given
* system32 DLL.
*
* @returns true if it is, false if not.
* @param pUniStr The zero terminated NT unicode string path.
* @param pszName The name of the system32 DLL.
*/
{
{
{
{
if (RTUtf16ICmpAscii(&pUniStr->Buffer[g_System32NtPath.UniStr.Length / sizeof(WCHAR) + 1], pszName) == 0)
return true;
}
}
}
return false;
}
/**
* Common code used for child and parent to make new threads exit immediately.
*
* This patches the LdrInitializeThunk code to call NtTerminateThread with
* STATUS_SUCCESS instead of doing the NTDLL initialization.
*
* @returns VBox status code.
* @param hProcess The process to do this to.
* @param pvLdrInitThunk The address of the LdrInitializeThunk code to
* override.
* @param pvNtTerminateThread The address of the NtTerminateThread function in
* the NTDLL instance we're patching. (Must be +/-
* 2GB from the thunk code.)
* @param pabBackup Where to back up the original instruction bytes
* at pvLdrInitThunk.
* @param cbBackup The size of the backup area. Must be 16 bytes.
* @param pErrInfo Where to return extended error information.
* Optional.
*/
static int supR3HardNtDisableThreadCreationEx(HANDLE hProcess, void *pvLdrInitThunk, void *pvNtTerminateThread,
{
SUP_DPRINTF(("supR3HardNtDisableThreadCreation: pvLdrInitThunk=%p pvNtTerminateThread=%p\n", pvLdrInitThunk, pvNtTerminateThread));
/*
* Back up the thunk code.
*/
if (!NT_SUCCESS(rcNt))
"supR3HardNtDisableThreadCreation: NtReadVirtualMemory/LdrInitializeThunk failed: %#x", rcNt);
/*
* Cook up replacement code that calls NtTerminateThread.
*/
#ifdef RT_ARCH_AMD64
*(int32_t *)&abReplacement[5] = (int32_t)((uintptr_t)pvNtTerminateThread - ((uintptr_t)pvLdrInitThunk + 9));
#elif defined(RT_ARCH_X86)
*(int32_t *)&abReplacement[5] = (int32_t)((uintptr_t)pvNtTerminateThread - ((uintptr_t)pvLdrInitThunk + 9));
#else
# error "Unsupported arch."
#endif
/*
* Install the replacment code.
*/
if (!NT_SUCCESS(rcNt))
"supR3HardNtDisableThreadCreationEx: NtProtectVirtualMemory/LdrInitializeThunk failed: %#x", rcNt);
rcNt = NtWriteVirtualMemory(hProcess, pvLdrInitThunk, abReplacement, sizeof(abReplacement), &cbIgnored);
if (!NT_SUCCESS(rcNt))
"supR3HardNtDisableThreadCreationEx: NtWriteVirtualMemory/LdrInitializeThunk failed: %#x", rcNt);
if (!NT_SUCCESS(rcNt))
"supR3HardNtDisableThreadCreationEx: NtProtectVirtualMemory/LdrInitializeThunk/2 failed: %#x", rcNt);
return VINF_SUCCESS;
}
/**
* Undo the effects of supR3HardNtDisableThreadCreationEx.
*
* @returns VBox status code.
* @param hProcess The process to do this to.
* @param pvLdrInitThunk The address of the LdrInitializeThunk code to
* override.
* @param pabBackup Where to back up the original instruction bytes
* at pvLdrInitThunk.
* @param cbBackup The size of the backup area. Must be 16 bytes.
* @param pErrInfo Where to return extended error information.
* Optional.
*/
static int supR3HardNtEnableThreadCreationEx(HANDLE hProcess, void *pvLdrInitThunk, uint8_t const *pabBackup, size_t cbBackup,
{
SUP_DPRINTF(("supR3HardNtEnableThreadCreation:\n"));
NTSTATUS rcNt = NtProtectVirtualMemory(hProcess, &pvProt, &cbProt, PAGE_EXECUTE_READWRITE, &fOldProt);
if (!NT_SUCCESS(rcNt))
"supR3HardNtDisableThreadCreationEx: NtProtectVirtualMemory/LdrInitializeThunk failed: %#x", rcNt);
if (!NT_SUCCESS(rcNt))
"supR3HardNtEnableThreadCreation: NtWriteVirtualMemory/LdrInitializeThunk[restore] failed: %#x",
rcNt);
if (!NT_SUCCESS(rcNt))
"supR3HardNtEnableThreadCreation: NtProtectVirtualMemory/LdrInitializeThunk[restore] failed: %#x",
rcNt);
return VINF_SUCCESS;
}
/**
* Disable thread creation for the current process.
*
* @remarks Doesn't really disables it, just makes the threads exit immediately
* without executing any real code.
*/
static void supR3HardenedWinDisableThreadCreation(void)
{
/* Cannot use the imported NtTerminateThread as it's pointing to our own
syscall assembly code. */
(void *)(uintptr_t)&LdrInitializeThunk,
(void *)(uintptr_t)pfnNtTerminateThread,
NULL /* pErrInfo*/);
}
/**
* Undoes the effects of supR3HardenedWinDisableThreadCreation.
*/
DECLHIDDEN(void) supR3HardenedWinEnableThreadCreation(void)
{
{
(void *)(uintptr_t)&LdrInitializeThunk,
if (RT_FAILURE(rc))
g_fSupInitThunkSelfPatched = false;
}
}
/*
* Child-Process Purification - release it from dubious influences.
*
* AV software and other things injecting themselves into the embryonic
* and budding process to intercept API calls and what not. Unfortunately
* this is also the behavior of viruses, malware and other unfriendly
* software, so we won't stand for it. AV software can scan our image
* as they are loaded via kernel hooks, that's sufficient. No need for
* matching half of NTDLL or messing with the import table of the
* process executable.
*/
typedef struct SUPR3HARDNTPUCH
{
/** Process handle. */
/** Primary thread handle. */
/** Error buffer. */
/** The address of NTDLL in the child. */
/** The address of NTDLL in this process. */
/** The basic process info. */
/** The probable size of the PEB. */
/** The pristine process environment block. */
typedef SUPR3HARDNTPUCH *PSUPR3HARDNTPUCH;
{
/*
* Not sure if any of the cracker software uses the PEB at this point, but
* just in case they do make some of the PEB fields a little less useful.
*/
/* Make ImageBaseAddress useless. */
#ifdef RT_ARCH_AMD64
#endif
/*
* Write the PEB.
*/
NTSTATUS rcNt = NtWriteVirtualMemory(pThis->hProcess, pThis->BasicInfo.PebBaseAddress, &Peb, pThis->cbPeb, &cbActualMem);
if (!NT_SUCCESS(rcNt))
return RTErrInfoSetF(pThis->pErrInfo, VERR_GENERAL_FAILURE, "NtWriteVirtualMemory/Peb failed: %#x", rcNt);
return VINF_SUCCESS;
}
/**
* Unmaps a DLL from the child process that was previously mapped by
* supR3HardNtPuChMapDllIntoChild.
*
* @returns Pointer to the DLL mapping on success, NULL on failure.
* @param pThis The child purification instance data.
* @param pvBase The base address of the mapping. Nothing done
* if NULL.
* @param pszShort The short name (for logging).
*/
static void supR3HardNtPuChUnmapDllFromChild(PSUPR3HARDNTPUCH pThis, PVOID pvBase, const char *pszShort)
{
if (pvBase)
{
if (!NT_SUCCESS(!rcNt))
SUP_DPRINTF(("supR3HardNtPuChTriggerInitialImageEvents: NtUnmapViewOfSection failed on %s: %#x (%p)\n",
}
}
/**
* Maps a DLL into the child process.
*
* @returns Pointer to the DLL mapping on success, NULL on failure.
* @param pThis The child purification instance data.
* @param pNtName The path to the DLL.
* @param pszShort The short name (for logging).
*/
static PVOID supR3HardNtPuChMapDllIntoChild(PSUPR3HARDNTPUCH pThis, PUNICODE_STRING pNtName, const char *pszShort)
{
InitializeObjectAttributes(&ObjAttr, pNtName, OBJ_CASE_INSENSITIVE, NULL /*hRootDir*/, NULL /*pSecDesc*/);
&ObjAttr,
&Ios,
NULL /* Allocation Size*/,
NULL /*EaBuffer*/,
0 /*EaLength*/);
if (NT_SUCCESS(rcNt))
if (NT_SUCCESS(rcNt))
{
if (NT_SUCCESS(rcNt))
{
if (NT_SUCCESS(rcNt))
SUP_DPRINTF(("supR3HardNtPuChTriggerInitialImageEvents: %s mapped at %p LB %#x\n", pszShort, pvRet, cbView));
else
{
SUP_DPRINTF(("supR3HardNtPuChTriggerInitialImageEvents: NtMapViewOfSection failed on %s: %#x\n", pszShort, rcNt));
}
}
else
SUP_DPRINTF(("supR3HardNtPuChTriggerInitialImageEvents: NtCreateSection failed on %s: %#x\n", pszShort, rcNt));
}
else
return pvRet;
}
/**
* Trigger the initial image events without actually initializing the process.
*
* This is a trick to force sysplant.sys to call its hand by tripping the image
* loaded event for the main executable and ntdll images. This will happen when
* the first thread in a process starts executing in PspUserThreadStartup. We
* create a second thread that quits immediately by means of temporarily
* replacing ntdll!LdrInitializeThunk by a NtTerminateThread call.
* (LdrInitializeThunk is called by way of an APC queued the thread is created,
* thus NtSetContextThread is of no use.)
*
* @returns VBox status code.
* @param pThis The child cleanup
* @param pErrInfo For extended error information.
*/
{
/*
* Use the on-disk image for the ntdll entrypoints here.
*/
if (RT_FAILURE(rc))
"LdrInitializeThunk", &uLdrInitThunk);
if (RT_FAILURE(rc))
"NtTerminateThread", &uNtTerminateThread);
if (RT_FAILURE(rc))
SUP_DPRINTF(("supR3HardNtPuChTriggerInitialImageEvents: uLdrInitThunk=%p uNtTerminateThread=%p\n",
/*
* Patch the child's LdrInitializeThunk to exit the thread immediately.
*/
rc = supR3HardNtDisableThreadCreationEx(pThis->hProcess, pvLdrInitThunk, (void *)(uintptr_t)uNtTerminateThread,
if (RT_FAILURE(rc))
return rc;
/*
* To further muddle the waters, we map the executable image and ntdll.dll
* a 2nd time into the process before we actually start executing the thread
* and trigger the genuine image load events.
*/
PVOID pvExe2 = supR3HardNtPuChMapDllIntoChild(pThis, &g_SupLibHardenedExeNtPath.UniStr, "executable[2nd]");
/*
* Create the thread, waiting 10 seconds for it to complete.
*/
NULL /* SecurityAttribs */,
FALSE /* CreateSuspended */,
0 /* ZeroBits */,
0 /* MaximumStackSize */,
0 /* CommittedStackSize */,
NULL /*Parameter*/ ,
&hThread2,
&Thread2Id);
if (NT_SUCCESS(rcNt))
{
}
/*
* Map kernel32.dll and kernelbase.dll (if applicable) into the process.
* This triggers should image load events that may set of AV activities
* that we'd rather see early than later.
*/
: NULL;
/*
* Fudge factor for letting kernel threads get a chance to mess up our
* process asynchronously.
*/
SUP_DPRINTF(("supR3HardNtPuChTriggerInitialImageEvents: Startup delay kludge #1: %u ms\n", GetTickCount() - dwStart));
/*
* Unmap the image we mapped into the guest above.
*/
/*
* Restore the original thunk code and protection.
* We do this after waiting as anyone trying to kick of threads in the
* process will get nothing done as long as our patch is in place.
*/
rc = supR3HardNtEnableThreadCreationEx(pThis->hProcess, pvLdrInitThunk, abBackup, sizeof(abBackup), pThis->pErrInfo);
if (RT_FAILURE(rc))
return rc;
return VINF_SUCCESS;
}
#if 0
static int supR3HardenedWinScratchChildMemory(HANDLE hProcess, void *pv, size_t cb, const char *pszWhat, PRTERRINFO pErrInfo)
{
if (!NT_SUCCESS(rcNt))
return RTErrInfoSetF(pErrInfo, VERR_GENERAL_FAILURE, "NtProtectVirtualMemory/%s (%p LB %#zx) failed: %#x",
return VINF_SUCCESS;
}
#endif
{
/*
* Make a copy of the pre-execution PEB.
*/
#if 0
/*
* There should not be any activation context, so if there is, we scratch the memory associated with it.
*/
int rc = 0;
if (RT_SUCCESS(rc) && Peb.ActivationContextData && !((uintptr_t)Peb.ActivationContextData & PAGE_OFFSET_MASK))
rc = supR3HardenedWinScratchChildMemory(hProcess, Peb.ActivationContextData, PAGE_SIZE, "ActivationContextData", pErrInfo);
if (RT_SUCCESS(rc) && Peb.ProcessAssemblyStorageMap && !((uintptr_t)Peb.ProcessAssemblyStorageMap & PAGE_OFFSET_MASK))
rc = supR3HardenedWinScratchChildMemory(hProcess, Peb.ProcessAssemblyStorageMap, PAGE_SIZE, "ProcessAssemblyStorageMap", pErrInfo);
if (RT_SUCCESS(rc) && Peb.SystemDefaultActivationContextData && !((uintptr_t)Peb.SystemDefaultActivationContextData & PAGE_OFFSET_MASK))
rc = supR3HardenedWinScratchChildMemory(hProcess, Peb.ProcessAssemblyStorageMap, PAGE_SIZE, "SystemDefaultActivationContextData", pErrInfo);
if (RT_SUCCESS(rc) && Peb.SystemAssemblyStorageMap && !((uintptr_t)Peb.SystemAssemblyStorageMap & PAGE_OFFSET_MASK))
rc = supR3HardenedWinScratchChildMemory(hProcess, Peb.SystemAssemblyStorageMap, PAGE_SIZE, "SystemAssemblyStorageMap", pErrInfo);
if (RT_FAILURE(rc))
return rc;
#endif
/*
* Clear compatibility and activation related fields.
*/
#if 0
/*Peb.Diff0.W6.IsProtectedProcess = 1;*/
#endif
/*
* Write back the PEB.
*/
NTSTATUS rcNt = NtWriteVirtualMemory(pThis->hProcess, pThis->BasicInfo.PebBaseAddress, &Peb, pThis->cbPeb, &cbActualMem);
if (!NT_SUCCESS(rcNt))
return RTErrInfoSetF(pThis->pErrInfo, VERR_GENERAL_FAILURE, "NtWriteVirtualMemory/Peb failed: %#x", rcNt);
return VINF_SUCCESS;
}
{
/*
* Find NTDLL in this process first and take that as a starting point.
*/
SUPR3HARDENED_ASSERT(pThis->uNtDllParentAddr != 0 && !(pThis->uNtDllParentAddr & PAGE_OFFSET_MASK));
/*
* Scan the virtual memory of the child.
*/
for (uint32_t i = 0; i < 1024; i++)
{
/* Query information. */
MEMORY_BASIC_INFORMATION MemInfo = { 0, 0, 0, 0, 0, 0, 0 };
(void const *)uPtrWhere,
&MemInfo,
sizeof(MemInfo),
&cbActual);
if (!NT_SUCCESS(rcNt))
break;
{
{
/* Get the image name. */
union
{
} uBuf;
&uBuf,
&cbActual);
if (NT_SUCCESS(rcNt))
{
{
SUP_DPRINTF(("supR3HardNtPuChFindNtdll: uNtDllParentAddr=%p uNtDllChildAddr=%p\n",
return;
}
}
}
}
/*
* Advance.
*/
break;
}
#ifdef DEBUG
#endif
}
{
/*
* Initialize the purifier instance data.
*/
if (!NT_SUCCESS(rcNt))
"NtQueryInformationProcess/ProcessBasicInformation failed: %#x", rcNt);
if (g_uNtVerCombined < SUP_NT_VER_W2K3)
else if (g_uNtVerCombined < SUP_NT_VER_VISTA)
else if (g_uNtVerCombined < SUP_NT_VER_W70)
else if (g_uNtVerCombined < SUP_NT_VER_W80)
else if (g_uNtVerCombined < SUP_NT_VER_W81)
else
SUP_DPRINTF(("supR3HardenedWinPurifyChild: PebBaseAddress=%p cbPeb=%#x\n", This.BasicInfo.PebBaseAddress, This.cbPeb));
rcNt = NtReadVirtualMemory(hProcess, This.BasicInfo.PebBaseAddress, &This.Peb, sizeof(This.Peb), &cbActualMem);
if (!NT_SUCCESS(rcNt))
/*
* Do the work, the last bit we tag along with the process verfication code.
*/
if (RT_SUCCESS(rc))
if (RT_SUCCESS(rc))
if (RT_SUCCESS(rc))
rc = supHardenedWinVerifyProcess(hProcess, hThread, SUPHARDNTVPKIND_CHILD_PURIFICATION, NULL /*pcFixes*/, pErrInfo);
return rc;
}
/**
* Does the actually respawning.
*
* @returns Never, will call exit or raise fatal error.
* @param iWhich Which respawn we're to check for, 1 being the
* first one, and 2 the second and final.
*
* @todo Split up this function.
*/
static int supR3HardenedWinDoReSpawn(int iWhich)
{
/*
* Set up security descriptors.
*/
#if 1
/*
* Configure the startup info and creation flags.
*/
if (1)
else
{
/** @todo experiment with protected process stuff later on. */
}
/*
* Construct the command line and launch the process.
*/
TRUE /*fInheritHandles*/,
NULL /*pwszzEnvironment*/,
NULL /*pwszCurDir*/,
"Error relaunching VirtualBox VM process: %u\n"
"Command line: '%ls'",
GetLastError(), pwszCmdLine);
SUP_DPRINTF(("supR3HardenedWinDoReSpawn(%d): New child %x.%x [kernel32].\n",
#else
/*
* Construct the process parameters.
*/
W32ImageName.Buffer = g_wszSupLibHardenedExePath; /* Yes the windows name for the process parameters. */
NULL /* DllPath - inherit from this process */,
NULL /* CurrentDirectory - inherit from this process */,
&CmdLine,
NULL /* Environment - inherit from this process */,
NULL /* WindowsTitle - none */,
NULL /* DesktopTitle - none. */,
NULL /* ShellInfo - none. */,
NULL /* RuntimeInfo - none (byte array for MSVCRT file info) */)
);
/** @todo this doesn't work. :-( */
NULL, //&ProcessSecAttrs,
NULL, //&ThreadSecAttrs,
NtCurrentProcess() /* ParentProcess */,
FALSE /*fInheritHandles*/,
NULL /* DebugPort */,
NULL /* ExceptionPort */,
if (!NT_SUCCESS(rcNt))
"Error relaunching VirtualBox VM process: %#x\n"
"Command line: '%ls'",
SUP_DPRINTF(("supR3HardenedWinDoReSpawn(%d): New child %x.%x [ntdll].\n",
#endif
#ifndef VBOX_WITHOUT_DEBUGGER_CHECKS
/*
* Apply anti debugger notification trick to the thread. (Also done in
* supR3HardenedWinInstallHooks.)
*/
if (!NT_SUCCESS(rcNt))
{
supR3HardenedError(rcNt, true /*fFatal*/, "NtSetInformationThread/ThreadHideFromDebugger failed: %#x\n", rcNt);
}
#endif
/*
* Clean up the process.
*/
if (RT_FAILURE(rc))
{
}
/*
* Start the process execution.
*/
ULONG cSuspendCount = 0;
/*
* Close the unrestricted access handles. Since we need to wait on the
* child process, we'll reopen the process with limited access before doing
* away with the process handle returned by CreateProcess.
*/
else
fRights, 0 /*HandleAttributes*/, 0);
if (rcNt == STATUS_ACCESS_DENIED)
SYNCHRONIZE, 0 /*HandleAttributes*/, 0);
if (!NT_SUCCESS(rcNt))
{
/* Failure is unacceptable, kill the process. */
supR3HardenedError(rcNt, false /*fFatal*/, "NtDuplicateObject failed on child process handle: %#x\n", rcNt);
NTSTATUS rcNtExit = NtQueryInformationProcess(hProcess, ProcessBasicInformation, &BasicInfo, sizeof(BasicInfo), NULL);
if (!fExitOk)
{
do
{
rcNtExit = NtQueryInformationProcess(hProcess, ProcessBasicInformation, &BasicInfo, sizeof(BasicInfo), NULL);
} while ( !fExitOk
&& ( rcNtWait == STATUS_TIMEOUT
|| rcNtWait == STATUS_USER_APC
|| rcNtWait == STATUS_ALERTED)
if (fExitOk)
"NtDuplicateObject failed and we failed to kill child: rcNt=%u rcNtWait=%u hProcess=%p\n",
}
"NtDuplicateObject failed on child process handle: %#x\n", rcNt);
}
/*
* Ditch the loader cache so we don't sit on too much memory while waiting.
*/
/*
* If this is the middle process, wait for both parent and child to quit.
*/
if (iWhich > 1)
{
rcNt = NtQueryInformationProcess(NtCurrentProcess(), ProcessBasicInformation, &BasicInfo, sizeof(BasicInfo), NULL);
if (NT_SUCCESS(rcNt))
{
}
#ifdef DEBUG
#endif
}
{
for (;;)
{
rcNt = NtWaitForMultipleObjects(2, &ahHandles[0], WaitAnyObject, TRUE /*Alertable*/, NULL /*pTimeout*/);
if ( rcNt == STATUS_WAIT_0
|| rcNt == STATUS_ABANDONED_WAIT_0
break;
if ( rcNt != STATUS_TIMEOUT
&& rcNt != STATUS_USER_APC
&& rcNt != STATUS_ALERTED)
}
}
else
{
/*
* Wait for the process to terminate.
*/
for (;;)
{
if ( rcNt == STATUS_WAIT_0
|| rcNt == STATUS_ABANDONED_WAIT_0)
break;
if ( rcNt != STATUS_TIMEOUT
&& rcNt != STATUS_USER_APC
&& rcNt != STATUS_ALERTED)
}
}
/*
* Proxy the termination code of the child, if it exited already.
*/
NTSTATUS rcNt2 = NtQueryInformationProcess(hProcWait, ProcessBasicInformation, &BasicInfo, sizeof(BasicInfo), NULL);
if ( !NT_SUCCESS(rcNt2)
SUP_DPRINTF(("supR3HardenedWinDoReSpawn(%d): Quitting: ExitCode=%#x rcNt=%#x\n", iWhich, BasicInfo.ExitStatus, rcNt));
}
/**
* Checks if the driver exists.
*
* This checks whether the driver is present in the /Driver object directory.
* Drivers being initialized or terminated will have an object there
*
* @returns true if it exists, false if not.
* @param pszDriver The driver name.
*/
static bool supR3HardenedWinDriverExists(const char *pszDriver)
{
/*
* Open the driver object directory.
*/
InitializeObjectAttributes(&ObjAttr, &NtDirName, OBJ_CASE_INSENSITIVE, NULL /*hRootDir*/, NULL /*pSecDesc*/);
#ifdef VBOX_STRICT
#endif
if (!NT_SUCCESS(rcNt))
return true;
/*
* Enumerate it, looking for the driver.
*/
bool fFound = true;
ULONG uObjDirCtx = 0;
do
{
FALSE /*ReturnSingleEntry */,
FALSE /*RestartScan*/,
&cbActual);
break;
{
{
fFound = true;
break;
}
/* Next directory entry. */
pObjDir++;
}
} while (!fFound);
/*
* Clean up and return.
*/
return fFound;
}
/**
* Open the stub device before the 2nd respawn.
*/
static void supR3HardenedWinOpenStubDevice(void)
{
/*
* Retry if we think driver might still be initializing (STATUS_NO_SUCH_DEVICE + \Drivers\VBoxDrv).
*/
{
InitializeObjectAttributes(&ObjAttr, &NtName, OBJ_CASE_INSENSITIVE, NULL /*hRootDir*/, NULL /*pSecDesc*/);
&ObjAttr,
&Ios,
NULL /* Allocation Size*/,
NULL /*EaBuffer*/,
0 /*EaLength*/);
if (NT_SUCCESS(rcNt))
/* The STATUS_NO_SUCH_DEVICE might be returned if the device is not
completely initialized. Delay a little bit and try again. */
if (rcNt != STATUS_NO_SUCH_DEVICE)
break;
break;
if (!supR3HardenedWinDriverExists("VBoxDrv"))
{
/** @todo Consider starting the VBoxdrv.sys service. Requires 2nd process
* though, rather complicated actually as CreateProcess causes all
* kind of things to happen to this process which would make it hard to
* pass the process verification tests... :-/ */
break;
}
if (iTry < 8)
else
}
if (!NT_SUCCESS(rcNt))
{
int rc = VERR_OPEN_FAILED;
else
{
const char *pszDefine;
switch (rcNt)
{
default: pszDefine = ""; break;
}
}
}
}
/**
* Called by the main code if supR3HardenedWinIsReSpawnNeeded returns @c true.
*
* @returns Program exit code.
*/
{
/*
* Before the 2nd respawn we set up a child protection deal with the
* support driver via /Devices/VBoxDrvStub.
*/
if (iWhich == 2)
/*
* Respawn the process with kernel protection for the new process.
*/
return supR3HardenedWinDoReSpawn(iWhich);
}
/**
* Checks if re-spawning is required, replacing the respawn argument if not.
*
* @returns true if required, false if not. In the latter case, the first
* argument in the vector is replaced.
* @param iWhich Which respawn we're to check for, 1 being the
* first one, and 2 the second and final.
* @param cArgs The number of arguments.
* @param papszArgs Pointer to the argument vector.
*/
{
if (cArgs < 1)
return true;
{
if (iWhich > 1)
return true;
}
{
if (iWhich < 2)
return false;
}
else
return true;
/* Replace the argument. */
return false;
}
/**
* Initializes the windows verficiation bits.
* @param fFlags The main flags.
*/
{
if (RT_FAILURE(rc))
if (!(fFlags & SUPSECMAIN_FLAGS_DONT_OPEN_DEV))
{
/*
* Do a self purification to cure avast's weird NtOpenFile write-thru
* change in GetBinaryTypeW change in kernel32. Unfortunately, avast
* uses a system thread to perform the process modifications, which
* means it's hard to make sure it had the chance to make them...
*
* We have to resort to kludge doing yield and sleep fudging for a
* number of milliseconds and schedulings before we can hope that avast
* and similar products have done what they need to do. If we do any
* fixes, we wait for a while again and redo it until we're clean.
*
* This is unfortunately kind of fragile.
*/
do
{
do
{
cSleeps++;
|| cSleeps < 8);
SUP_DPRINTF(("supR3HardenedWinInit: Startup delay kludge #2/%u: %u ms, %u sleeps\n",
cFixes = 0;
rc = supHardenedWinVerifyProcess(NtCurrentProcess(), NtCurrentThread(), SUPHARDNTVPKIND_SELF_PURIFICATION,
} while ( RT_SUCCESS(rc)
&& cFixes > 0
&& ++iLoop < 8);
/*
* Install the hooks.
*/
}
#ifndef VBOX_WITH_VISTA_NO_SP
/*
* Complain about Vista w/o service pack if we're launching a VM.
*/
if ( !(fFlags & SUPSECMAIN_FLAGS_DONT_OPEN_DEV)
"Window Vista without any service pack installed is not supported. Please install the latest service pack.");
#endif
}
/**
* Converts the Windows command line string (UTF-16) to an array of UTF-8
* arguments suitable for passing to main().
*
* @returns Pointer to the argument array.
* @param pawcCmdLine The UTF-16 windows command line to parse.
* @param cwcCmdLine The length of the command line.
* @param pcArgs Where to return the number of arguments.
*/
{
/*
* Convert the command line string to UTF-8.
*/
char *pszCmdLine = NULL;
/*
* Parse the command line, carving argument strings out of it.
*/
int cArgs = 0;
int cArgsAllocated = 4;
char *pszSrc = pszCmdLine;
for (;;)
{
/* skip leading blanks. */
while (suplibCommandLineIsArgSeparator(ch))
if (!ch)
break;
/* Add argument to the vector. */
{
cArgsAllocated *= 2;
}
/* Unquote and unescape the string. */
bool fQuoted = false;
do
{
if (ch == '"')
else
{
unsigned cSlashes = 0;
cSlashes++;
if (ch == '"')
{
while (cSlashes >= 2)
{
cSlashes -= 2;
*pszDst++ = '\\';
}
if (cSlashes)
*pszDst++ = '"';
else
}
else
{
pszSrc--;
while (cSlashes-- > 0)
*pszDst++ = '\\';
}
}
/* Terminate the argument. */
*pszDst = '\0';
if (!ch)
break;
}
return papszArgs;
}
/**
* The executable entry point.
*
* This is normally taken care of by the C runtime library, but we don't want to
* get involved with anything as complicated like the CRT in this setup. So, we
* it everything ourselves, including parameter parsing.
*/
{
/*
* Initialize the NTDLL API wrappers. This aims at bypassing patched NTDLL
* in all the processes leading up the VM process.
*/
/*
* After having resolved imports we patch the LdrInitializeThunk code so
* that it's more difficult to invade our privacy by CreateRemoteThread.
* We'll re-enable this after opening the driver or temporarily while respawning.
*/
/*
* Init g_uNtVerCombined. (The code is shared with SUPR3.lib and lives in
*/
/*
* Convert the arguments to UTF-8 and open the log file if specified.
* This must be done as early as possible since the code below may fail.
*/
int cArgs;
char **papszArgs = suplibCommandLineToArgvWStub(pCmdLineStr->Buffer, pCmdLineStr->Length / sizeof(WCHAR), &cArgs);
/*
* Get the executable name.
*/
"The executable path is too long.");
/* The NT version. */
HANDLE hFile = CreateFileW(g_wszSupLibHardenedExePath, GENERIC_READ, FILE_SHARE_READ, NULL /*pSecurityAttributes*/,
supR3HardenedFatalMsg("suplibHardenedWindowsMain", kSupInitOp_Integrity, RTErrConvertFromWin32(GetLastError()),
"Error opening the executable: %u (%ls).", GetLastError());
if (!NT_SUCCESS(rcNt))
supR3HardenedFatalMsg("suplibHardenedWindowsMain", kSupInitOp_Integrity, RTErrConvertFromNtStatus(rcNt),
/* The NT executable name offset / dir path length. */
while ( g_offSupLibHardenedExeNtName > 1
/*
* Call the C/C++ main function.
*/
SUP_DPRINTF(("Calling main()\n"));
/*
* Exit the process (never return).
*/
}