You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
288 lines
10 KiB
288 lines
10 KiB
/***
|
|
*eh3valid.c - Validate the registration node for _except_handler3
|
|
*
|
|
* Copyright (c) 2002, Microsoft Corporation. All rights reserved.
|
|
*
|
|
*Purpose:
|
|
* Defines _ValidateEH3RN used to guard against hacker attacks which
|
|
* attempt to use _except_handler3 to sidestep the .sxdata OS checks.
|
|
*
|
|
*Revision History:
|
|
* 03-18-02 PML File created
|
|
* 04-27-02 PML Perf: keep list of valid scopetables (vs7#522476)
|
|
*
|
|
*******************************************************************************/
|
|
|
|
#include <windows.h>
|
|
|
|
typedef struct _SCOPETABLE_ENTRY {
|
|
DWORD EnclosingLevel;
|
|
PVOID FilterFunc;
|
|
PVOID HandlerFunc;
|
|
} SCOPETABLE_ENTRY, *PSCOPETABLE_ENTRY;
|
|
|
|
typedef struct _EH3_EXCEPTION_REGISTRATION {
|
|
//
|
|
// These are at negative offsets from the struct start:
|
|
//
|
|
// DWORD SavedESP;
|
|
// PEXCEPTION_POINTERS XPointers;
|
|
//
|
|
// Common to all exception registration nodes:
|
|
//
|
|
struct _EH3_EXCEPTION_REGISTRATION *Next;
|
|
PVOID ExceptionHandler;
|
|
//
|
|
// Private to _except_handler3's registration node:
|
|
//
|
|
PSCOPETABLE_ENTRY ScopeTable;
|
|
DWORD TryLevel;
|
|
} EH3_EXCEPTION_REGISTRATION, *PEH3_EXCEPTION_REGISTRATION;
|
|
|
|
#define SAVED_ESP(pRN) (((PVOID *)pRN)[-2])
|
|
|
|
#define EMPTY_LEVEL ((DWORD)-1)
|
|
|
|
#define SUCCESS (1)
|
|
#define FAILURE (0)
|
|
#define OPTIONAL_FAILURE (-1)
|
|
|
|
#define PAGE_SIZE 0x1000 // x86 uses 4K pages
|
|
#define VALID_SIZE 16
|
|
|
|
static PVOID rgValidPages[VALID_SIZE];
|
|
static int nValidPages;
|
|
static LONG lModifying; // nonzero if rgValidPages being modified
|
|
|
|
/***
|
|
*int _ValidateEH3RN - check validity of _except_handler3 registration node
|
|
*
|
|
*Purpose:
|
|
* Attempt to intercept hacker attacks that try to use an artificial
|
|
* _except_handler3 registration node to exploit a buffer overrun or
|
|
* other security bug to inject exploit code.
|
|
*
|
|
*Entry:
|
|
* pRN - pointer to _except_handler3 exception registration node
|
|
*
|
|
*Return:
|
|
* >0 All checks passed, scopetable is validated.
|
|
* 0 A required check failed and the exception should be rejected.
|
|
* <0 An optional check failed and the exception should be rejected if
|
|
* operating under these stricter tests.
|
|
*
|
|
* The optional checks only permit scopetables which are found inside
|
|
* MEM_IMAGE pages that are currently unwritable, or started that way
|
|
* according to the section descriptors.
|
|
*
|
|
*******************************************************************************/
|
|
|
|
int _ValidateEH3RN(PEH3_EXCEPTION_REGISTRATION pRN)
|
|
{
|
|
PNT_TIB pTIB;
|
|
PSCOPETABLE_ENTRY pScopeTable;
|
|
DWORD level;
|
|
int nFilters;
|
|
MEMORY_BASIC_INFORMATION mbi;
|
|
PIMAGE_DOS_HEADER pDOSHeader;
|
|
PIMAGE_NT_HEADERS pNTHeader;
|
|
PIMAGE_OPTIONAL_HEADER pOptHeader;
|
|
DWORD rvaScopeTable;
|
|
PIMAGE_SECTION_HEADER pSection;
|
|
unsigned int iSection;
|
|
PVOID pScopePage, pTmp;
|
|
int iValid, iValid2;
|
|
|
|
//
|
|
// Scopetable pointer must be DWORD aligned
|
|
//
|
|
pScopeTable = pRN->ScopeTable;
|
|
if (((DWORD_PTR)pScopeTable & 0x3) != 0)
|
|
return FAILURE;
|
|
|
|
//
|
|
// Scopetable cannot be located on the stack
|
|
//
|
|
__asm {
|
|
mov eax, fs:offset NT_TIB.Self
|
|
mov pTIB, eax
|
|
}
|
|
if ((PVOID)pScopeTable >= pTIB->StackLimit &&
|
|
(PVOID)pScopeTable < pTIB->StackBase)
|
|
return FAILURE;
|
|
|
|
//
|
|
// If not nested in guarded block, then nothing left to check
|
|
//
|
|
if (pRN->TryLevel == EMPTY_LEVEL)
|
|
return SUCCESS;
|
|
|
|
//
|
|
// Ensure all scopetable entries up to current try level are properly
|
|
// nested (parent level must be the empty state or a lower level than
|
|
// the one being checked).
|
|
//
|
|
nFilters = 0;
|
|
for (level = 0; level <= pRN->TryLevel; ++level)
|
|
{
|
|
DWORD enclosing = pScopeTable[level].EnclosingLevel;
|
|
if (enclosing != EMPTY_LEVEL && enclosing >= level)
|
|
return FAILURE;
|
|
if (pScopeTable[level].FilterFunc != NULL)
|
|
++nFilters;
|
|
}
|
|
|
|
//
|
|
// If the scopetable had any __except filters, make sure the saved ESP
|
|
// pointer is on the stack below the registration node
|
|
//
|
|
if (nFilters != 0 &&
|
|
(SAVED_ESP(pRN) < pTIB->StackLimit ||
|
|
SAVED_ESP(pRN) >= (PVOID)pRN) )
|
|
return FAILURE;
|
|
|
|
//
|
|
// Before validating the scopetable pointer, check if we've already
|
|
// validated a pointer on the same page, to avoid the expensive call
|
|
// to VirtualQuery. If the page is found in the list of valid pages,
|
|
// move it to the front of the list.
|
|
//
|
|
pScopePage = (PVOID)((DWORD_PTR)pScopeTable & ~(PAGE_SIZE - 1));
|
|
for (iValid = 0; iValid < nValidPages; ++iValid)
|
|
{
|
|
if (rgValidPages[iValid] == pScopePage)
|
|
{
|
|
// Found - move entry to start of valid list, unless some other
|
|
// thread is already updating the table
|
|
if (iValid > 0 && InterlockedExchange(&lModifying, 1) == 0)
|
|
{
|
|
if (rgValidPages[iValid] != pScopePage)
|
|
{
|
|
// Entry has been moved by another thread; find it
|
|
for (iValid = nValidPages - 1; iValid >= 0; --iValid)
|
|
if (rgValidPages[iValid] == pScopePage)
|
|
break;
|
|
if (iValid < 0)
|
|
{
|
|
// Entry no longer on list, add it back
|
|
if (nValidPages < VALID_SIZE)
|
|
++nValidPages;
|
|
iValid = nValidPages - 1;
|
|
}
|
|
else if (iValid == 0)
|
|
{
|
|
// Entry already moved to correct position
|
|
InterlockedExchange(&lModifying, 0);
|
|
return SUCCESS;
|
|
}
|
|
}
|
|
for (iValid2 = 0; iValid2 <= iValid; ++iValid2)
|
|
{
|
|
// Move elements before found entry back a position
|
|
// and store entry in 1st position
|
|
pTmp = rgValidPages[iValid2];
|
|
rgValidPages[iValid2] = pScopePage;
|
|
pScopePage = pTmp;
|
|
}
|
|
InterlockedExchange(&lModifying, 0);
|
|
}
|
|
return SUCCESS;
|
|
}
|
|
}
|
|
|
|
//
|
|
// It's an optional failure if the scopetable is not located inside an
|
|
// image. If the scopetable is in an image, it must not be in a writable
|
|
// section. First check if the memory is not currently marked writable.
|
|
//
|
|
if (VirtualQuery(pScopeTable, &mbi, sizeof mbi) == 0 ||
|
|
mbi.Type != MEM_IMAGE)
|
|
return OPTIONAL_FAILURE;
|
|
|
|
if ((mbi.Protect & (PAGE_READWRITE |
|
|
PAGE_WRITECOPY |
|
|
PAGE_EXECUTE_READWRITE |
|
|
PAGE_EXECUTE_WRITECOPY)) == 0)
|
|
goto exit_success;
|
|
|
|
//
|
|
// Scopetable is inside an image, but in memory marked writable. Still
|
|
// might be OK, if the memory started unwritable but was later changed
|
|
// by VirtualProtect. See if we're in a normal NT PE executable image,
|
|
// and if yes, find the image section holding the scopetable and check
|
|
// its characteristics.
|
|
//
|
|
// This code assumes that calling VirtualQuery on a pointer anywhere inside
|
|
// an image will return an AllocationBase equal to the start of the image,
|
|
// i.e. a single VirtualAlloc was used to allocate the entire image range.
|
|
// If we don't see a PE executable as expected, treat it as an optional
|
|
// failure.
|
|
//
|
|
pDOSHeader = (PIMAGE_DOS_HEADER)mbi.AllocationBase;
|
|
if (pDOSHeader->e_magic != IMAGE_DOS_SIGNATURE)
|
|
return OPTIONAL_FAILURE;
|
|
|
|
pNTHeader = (PIMAGE_NT_HEADERS)((PBYTE)pDOSHeader + pDOSHeader->e_lfanew);
|
|
if (pNTHeader->Signature != IMAGE_NT_SIGNATURE)
|
|
return OPTIONAL_FAILURE;
|
|
|
|
pOptHeader = (PIMAGE_OPTIONAL_HEADER)&pNTHeader->OptionalHeader;
|
|
if (pOptHeader->Magic != IMAGE_NT_OPTIONAL_HDR_MAGIC)
|
|
return OPTIONAL_FAILURE;
|
|
|
|
//
|
|
// Looks like a valid PE executable, find the section holding the
|
|
// scopetable. We make no assumptions here about the sort order of the
|
|
// section descriptors (though they always appear to be sorted by
|
|
// ascending section RVA).
|
|
//
|
|
rvaScopeTable = (DWORD)((PBYTE)pScopeTable - (PBYTE)pDOSHeader);
|
|
for (iSection = 0, pSection = IMAGE_FIRST_SECTION(pNTHeader);
|
|
iSection < pNTHeader->FileHeader.NumberOfSections;
|
|
++iSection, ++pSection)
|
|
{
|
|
if (rvaScopeTable >= pSection->VirtualAddress &&
|
|
rvaScopeTable < pSection->VirtualAddress +
|
|
pSection->Misc.VirtualSize)
|
|
//
|
|
// Scopetable section found, return SUCCESS if not writable
|
|
//
|
|
if (pSection->Characteristics & IMAGE_SCN_MEM_WRITE)
|
|
return FAILURE;
|
|
goto exit_success;
|
|
}
|
|
|
|
//
|
|
// Scopetable never found in any section, issue an optional failure
|
|
//
|
|
return OPTIONAL_FAILURE;
|
|
|
|
exit_success:
|
|
//
|
|
// Record the validated scopetable page in the valid list. Only allow one
|
|
// thread at a time to modify the list, and discard any attempted updates
|
|
// from other threads.
|
|
//
|
|
if (InterlockedExchange(&lModifying, 1) != 0)
|
|
// another thread is already updating the table, skip this update
|
|
return SUCCESS;
|
|
for (iValid = nValidPages; iValid > 0; --iValid)
|
|
if (rgValidPages[iValid - 1] == pScopePage)
|
|
break;
|
|
if (iValid == 0)
|
|
{
|
|
// normal case - page not found in table, add it as first entry
|
|
// If page found, it was just added, so don't bother updating table
|
|
iValid = min(VALID_SIZE-1, nValidPages);
|
|
for (iValid2 = 0; iValid2 <= iValid; ++iValid2)
|
|
{
|
|
pTmp = rgValidPages[iValid2];
|
|
rgValidPages[iValid2] = pScopePage;
|
|
pScopePage = pTmp;
|
|
}
|
|
if (nValidPages < VALID_SIZE)
|
|
++nValidPages;
|
|
}
|
|
InterlockedExchange(&lModifying, 0);
|
|
return SUCCESS;
|
|
}
|