SUPR3HardenedMainImports-win.cpp revision 9f997e760f610c92e3a365be21ead6972bc46130
/* $Id$ */
/** @file
* VirtualBox Support Library - Hardened Main, Windows Import Trickery.
*/
/*
* 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 <iprt/initterm.h>
#include "SUPLibInternal.h"
#include "SUPHardenedVerify-win.h"
/*******************************************************************************
* Defined Constants And Macros *
*******************************************************************************/
do { static const char s_szWhere[] = a_szWhere; *(char *)(uintptr_t)(a_id) += 1; __debugbreak(); } while (0)
#else
#endif
/*******************************************************************************
* Defined Constants And Macros *
*******************************************************************************/
/**
* Import function entry.
*/
typedef struct SUPHNTIMPFUNC
{
/** The name of the function we're importing. */
const char *pszName;
/** Where to store the function address (think __imp_ApiName). */
/** Pointer to an early dummy function for imports that aren't available
* during early process initialization. */
/** Indicates whether this is an optional import and failure to locate it
* should set it to NULL instead of freaking out. */
bool fOptional;
/** Pointer to an import table entry. */
typedef SUPHNTIMPFUNC const *PCSUPHNTIMPFUNC;
/**
* Information for constructing a direct system call.
*/
typedef struct SUPHNTIMPSYSCALL
{
/** Where to store the system call number.
* NULL if this import doesn't stupport direct system call. */
/** Assembly system call routine, type 1. */
#ifdef RT_ARCH_X86
/** Assembly system call routine, type 2. */
/** The parameter size in bytes for a standard call. */
#endif
/** Pointer to a system call entry. */
typedef SUPHNTIMPSYSCALL const *PCSUPHNTIMPSYSCALL;
/**
* Import DLL.
*
* This contains both static (like name & imports) and runtime information (like
* load and export table locations).
*/
typedef struct SUPHNTIMPDLL
{
/** @name Static data.
* @{ */
const wchar_t *pwszName;
const char *pszName;
/** Array running parallel to paImports if present. */
/** @} */
/** The image base. */
uint8_t const *pbImageBase;
/** The NT headers. */
/** The end of the section headers. */
/** The end of the image. */
/** Offset of the export directory. */
/** Size of the export directory. */
/** Exported functions and data by ordinal (RVAs). */
uint32_t const *paoffExports;
/** The number of exports. */
/** The number of exported names. */
/** Pointer to the array of exported names (RVAs to strings). */
uint32_t const *paoffNamedExports;
/** Array parallel to paoffNamedExports with the corresponding ordinals
* (indexes into paoffExports). */
uint16_t const *pau16NameOrdinals;
/** Number of patched export table entries. */
} SUPHNTIMPDLL;
/** Pointer to an import DLL entry. */
typedef SUPHNTIMPDLL *PSUPHNTIMPDLL;
/*
* Declare assembly symbols.
*/
#define SUPHARNT_IMPORT_STDCALL_EARLY_OPTIONAL(a_Name, a_cbParamsX86) SUPHARNT_IMPORT_STDCALL_EARLY(a_Name, a_cbParamsX86)
#include "import-template-ntdll.h"
#include "import-template-kernel32.h"
/*
* Import functions.
*/
static const SUPHNTIMPFUNC g_aSupNtImpNtDllFunctions[] =
{
#include "import-template-ntdll.h"
};
static const SUPHNTIMPFUNC g_aSupNtImpKernel32Functions[] =
{
#include "import-template-kernel32.h"
};
/*
* Syscalls in ntdll.
*/
#ifdef RT_ARCH_AMD64
#elif defined(RT_ARCH_X86)
{ &RT_CONCAT(g_uApiNo, a_Name), &RT_CONCAT(a_Name,_SyscallType1), &RT_CONCAT(a_Name, _SyscallType2), a_cbParamsX86 },
#endif
#define SUPHARNT_IMPORT_STDCALL_EARLY(a_Name, a_cbParamsX86) SUPHARNT_IMPORT_STDCALL(a_Name, a_cbParamsX86)
#define SUPHARNT_IMPORT_STDCALL_EARLY_OPTIONAL(a_Name, a_cbParamsX86) SUPHARNT_IMPORT_STDCALL(a_Name, a_cbParamsX86)
static const SUPHNTIMPSYSCALL g_aSupNtImpNtDllSyscalls[] =
{
#include "import-template-ntdll.h"
};
/**
* All the DLLs we import from.
* @remarks Code ASSUMES that ntdll is the first entry.
*/
static SUPHNTIMPDLL g_aSupNtImpDlls[] =
{
{ L"ntdll.dll", "ntdll.dll", RT_ELEMENTS(g_aSupNtImpNtDllFunctions), g_aSupNtImpNtDllFunctions, g_aSupNtImpNtDllSyscalls },
{ L"kernel32.dll", "kernel32.dll", RT_ELEMENTS(g_aSupNtImpKernel32Functions), g_aSupNtImpKernel32Functions, NULL },
};
{
{
PLDR_DATA_TABLE_ENTRY pLdrEntry = RT_FROM_MEMBER(pListEntry, LDR_DATA_TABLE_ENTRY, InMemoryOrderLinks);
&& ( pLdrEntry->FullDllName.Buffer[(pLdrEntry->FullDllName.Length - cbName) / sizeof(WCHAR) - 1] == '\\'
|| pLdrEntry->FullDllName.Buffer[(pLdrEntry->FullDllName.Length - cbName) / sizeof(WCHAR) - 1] == '/')
&& RTUtf16ICmpAscii(&pLdrEntry->FullDllName.Buffer[(pLdrEntry->FullDllName.Length - cbName) / sizeof(WCHAR)],
{
return;
}
cLoops++;
}
else
#else
#endif
}
{
/*
* Locate the PE header, do some basic validations.
*/
{
}
"%ls: Unexpected optional header size: %#x", pDll->pwszName, pNtHdrs->FileHeader.SizeOfOptionalHeader);
"%ls: Unexpected number of RVA and sizes: %#x", pDll->pwszName, pNtHdrs->OptionalHeader.NumberOfRvaAndSizes);
+ sizeof(*pNtHdrs)
/*
* Find the export directory.
*/
"%ls: Missing or invalid export directory: %#lx LB %#x", pDll->pwszName, ExpDir.VirtualAddress, ExpDir.Size);
IMAGE_EXPORT_DIRECTORY const *pExpDir = (IMAGE_EXPORT_DIRECTORY const *)&pDll->pbImageBase[ExpDir.VirtualAddress];
|| pExpDir->AddressOfFunctions + pDll->cExports * sizeof(uint32_t) > pNtHdrs->OptionalHeader.SizeOfImage)
|| pExpDir->AddressOfNames + pExpDir->NumberOfNames * sizeof(uint32_t) > pNtHdrs->OptionalHeader.SizeOfImage)
|| pExpDir->AddressOfNameOrdinals + pExpDir->NumberOfNames * sizeof(uint32_t) > pNtHdrs->OptionalHeader.SizeOfImage)
}
{
/*
* Binary search.
*/
{
if (iDiff > 0) /* pszExpName > pszSymbol: search chunck before i */
else if (iDiff < 0) /* pszExpName < pszSymbol: search chunk after i */
else /* pszExpName == pszSymbol */
{
{
/* detect export table patching. */
pDll->cPatchedExports++;
{
return NULL;
}
/* Forwarder. */
}
"%ls: Name ordinal for '%s' is out of bounds: %#x (max %#x)",
return NULL;
}
}
return NULL;
}
static void supR3HardenedDirectSyscall(PSUPHNTIMPDLL pDll, PCSUPHNTIMPFUNC pImport, PCSUPHNTIMPSYSCALL pSyscall,
{
/*
* Skip non-syscall entries.
*/
return;
/*
* Locate the virgin bits.
*/
int rc = RTLdrGetSymbolEx(pLdrEntry->hLdrMod, pbBits, (uintptr_t)pDll->pbImageBase, UINT32_MAX, pImport->pszName, &uValue);
if (RT_FAILURE(rc))
{
return;
}
/*
* Parse the code and extract the API call number.
*/
#ifdef RT_ARCH_AMD64
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] */
//&& pbFunction[ 4] == 0xZZ
//&& pbFunction[ 5] == 0xYY
{
return;
}
#else
/* 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. */
//&& pbFunction[ 1] <= 0xZZ
//&& pbFunction[ 2] <= 0xYY
{
)
)
{
return;
}
)
)
{
return;
}
}
#endif
/*
* Failed to parse it.
*/
"%ls: supHardNtLdrCacheOpen failed: '%s': %.16Rhxs",
}
/**
* Resolves NtDll functions we can trust calling before process init.
*
* @param uNtDllAddr The address of the NTDLL.
*/
{
/*
* NTDLL is the first entry in the list.
*/
{
const char *pszForwarder = supR3HardenedResolveImport(&g_aSupNtImpDlls[0], &g_aSupNtImpDlls[0].paImports[i]);
if (pszForwarder)
"ntdll: Failed to resolve forwarder '%s'.", pszForwarder);
}
else
/*
* Pointer the other imports at the early init stubs.
*/
else
}
/**
* Resolves imported functions, esp. system calls from NTDLL.
*
* This crap is necessary because there are sandboxing products out there that
* will mess with system calls we make, just like any other wannabe userland
* rootkit. Kudos to microsoft for not providing a generic system call hook API
* in the kernel mode, which I guess is what forcing these kind of products to
* do ugly userland hacks that doesn't really hold water.
*/
DECLHIDDEN(void) supR3HardenedWinInitImports(void)
{
/*
* Find the DLLs we will be needing first (forwarders).
*/
{
}
/*
* Resolve the functions.
*/
{
const char *pszForwarder = supR3HardenedResolveImport(&g_aSupNtImpDlls[iDll], &g_aSupNtImpDlls[iDll].paImports[i]);
if (pszForwarder)
{
else if (cchDllName == sizeof("kernelbase") - 1 && RTStrNICmp(pszForwarder, RT_STR_TUPLE("kernelbase")) == 0)
else
}
}
/*
* Check out system calls and try do them directly if we can.
* In order to do this though, we need to access the DLL on disk as we
* cannot trust the memory content to be unpatched.
*
* Note! It's too early to validate any signatures.
*/
{
if (RT_SUCCESS(rc))
{
rc = supHardNtLdrCacheEntryGetBits(pLdrEntry, &pbBits, (uintptr_t)g_aSupNtImpDlls[iDll].pbImageBase, NULL, NULL,
NULL /*pErrInfo*/);
if (RT_SUCCESS(rc))
{
}
else
}
else
}
/*
* Use the on disk image to avoid export table patching. Currently
* ignoring errors here as can live normally without this step.
*/
{
if (RT_SUCCESS(rc))
{
rc = supHardNtLdrCacheEntryGetBits(pLdrEntry, &pbBits, (uintptr_t)g_aSupNtImpDlls[iDll].pbImageBase, NULL, NULL,
NULL /*pErrInfo*/);
if (RT_SUCCESS(rc))
{
if (RT_SUCCESS(rc))
}
}
}
#if 0 /* Win7/32 ntdll!LdrpDebugFlags. */
#endif
}
/**
* Gets the address of a procedure in a DLL, ignoring our own syscall
* implementations.
*
* Currently restricted to NTDLL and KERNEL32
*
* @returns The procedure address.
* @param pszDll The DLL name.
* @param pszProcedure The procedure name.
*/
{
/*
* Look the DLL up in the import DLL table.
*/
{
if (RT_SUCCESS(rc))
{
rc = supHardNtLdrCacheEntryGetBits(pLdrEntry, &pbBits, (uintptr_t)g_aSupNtImpDlls[iDll].pbImageBase, NULL, NULL,
NULL /*pErrInfo*/);
if (RT_SUCCESS(rc))
{
if (RT_SUCCESS(rc))
SUP_DPRINTF(("supR3HardenedWinGetRealDllSymbol: Error getting %s in %s -> %Rrc\n", pszProcedure, pszDll, rc));
}
else
SUP_DPRINTF(("supR3HardenedWinGetRealDllSymbol: supHardNtLdrCacheEntryAllocBits failed on %s: %Rrc\n",
}
else
SUP_DPRINTF(("supR3HardenedWinGetRealDllSymbol: supHardNtLdrCacheOpen failed on %s: %Rrc\n",
/* Complications, just call GetProcAddress. */
return NULL;
}
supR3HardenedFatal("supR3HardenedWinGetRealDllSymbol: Unknown DLL %s (proc: %s)\n", pszDll, pszProcedure);
return NULL;
}