Introduction
In September 2001, under The Hood column in MSDN Magazine, Matt Pietrek covered a new feature introduced in Windows XP - Vectored Exception Handling (VEH). Matt explained in detail the VEH and gave us a useful example. When I examined this issue in Windows XP SP2, the most popular OS currently, I found some problems.
First, the demo program crashes when I run it. As follows:
According to the "Application Error" dialog, we know the error is a single step exception. I examined the source carefully, but couldn't find any problems. When I attached the Visual Studio .NET 2003 debugger to the crashed program, I found the error address is the second instruction of the LoadLibraryExW
function. Why is the single step flag still here? Didn't we have it removed? Finally, I have found the reason. In the previous Windows Operation System, almost all system functions have a standard stack frame. The first instruction of these functions is "PUSH EBP
" and its opcode is 55h, that is 1 byte. But in Windows XP SP2, the standard prolog code for the SEH stack frame of the system function has been replaced with the _SEH_prolog
function. I have found that the first instruction of these functions push the count of memory that local variables of these functions take into stack. For LoadLibraryExW
, it's "PUSH 34h
" and its opcode is 6Ah 34h, that is 2 bytes. In the demo program, if the single step flag wants to be removed, it must satisfy two conditions: first, the exception code must be STATUS_SINGLE_STEP
; second, the exception address must be the next address of LoadLibraryExW
's address. But it's impossible in Windows XP SP2, because the first instruction of LoadLibraryExW
is 2 bytes. If we replace 1 in the source with 2, we can solve this problem.
Second, in that article, Matt said that the Vectored Exception Handling linked list is a "Circular Linked List", but I have found it is a "Doubly Linked List". Moreover, the RtlpCalloutEntryList
global variable is the head of this linked list. This global variable is a LIST_ENTRY
structure (8 bytes), not a VECTORED_EXCEPTION_NODE
pointer. How did I find this? When I used SoftICE to see its value, I found the following:
Obviously, the variable takes 8 bytes. When you think of the style that Windows processes the linked list, you will get it.
As is well known, Windows XP SP2 enhances the security of the system. Many globally available pointers are encoded so that it is difficult to use these pointers to exploit. Exception handling is the first to be affected. Windows XP SP2 supplies a common way to do this. It is the EncodePointer
API. In fact, this API is forwarded to RtlEncodePointer
in NTDLL.DLL. Of course, there is DecodePointer
, and it's forwarded to RtlDecodePointer
in NTDLL.DLL.
Based on Matt's pseudocode, I have written the pseudocode for these API:
VEH.H file:
typedef struct _VECTORED_EXCEPTION_NODE {
LIST_ENTRY ListEntry;
PVECTORED_EXCEPTION_HANDLER pfnHandler;
} VECTORED_EXCEPTION_NODE, *PVECTORED_EXCEPTION_NODE;
RTL_CRITICAL_SECTION RtlpCalloutEntryLock;
LIST_ENTRY RtlpCalloutEntryList;
VEH.C file:
#include <ntddk.h>
#include "veh.h"
PVOID WINAPI RtlAddVectoredExceptionHandler(
ULONG First,
PVECTORED_EXCEPTION_HANDLER Handler)
{
PVECTORED_EXCEPTION_NODE pCurrentNode = (PVECTORED_EXCEPTION_NODE)
RtlAllocateHeap( GetProcessHeap(), 0, sizeof(VECTORED_EXCEPTION_NODE) );
if (!pCurrentNode)
{
return 0;
}
pCurrentNode->pfnHandler = RtlEncodePointer(Handler);
RtlEnterCriticalSection(&RtlpCalloutEntryLock);
if (First)
{
InsertHeadList(&RtlpCalloutEntryList, &pCurrentNode->ListEntry);
}
else
{
InsertTailList(&RtlpCalloutEntryList, &pCurrentNode->ListEntry);
}
RtlLeaveCriticalSection(&RtlpCalloutEntryLock);
return pCurrentNode;
}
ULONG WINAPI RemoveVectoredExceptionHandler(PVOID Handler)
{
PVECTORED_EXCEPTION_NODE pCurrentNode, pRemovedNode;
BOOL bHandlerExist = FALSE;
RtlEnterCriticalSection(&RtlpCalloutEntryLock);
for ((PLIST_ENTRY)pCurrentNode = RtlpCalloutEntryList.Flink;
(PLIST_ENTRY)pCurrentNode != &RtlpCalloutEntryList;
(PLIST_ENTRY)pCurrentNode = pCurrentNode->ListEntry.Flink)
{
if (pCurrentNode == (PVECTORED_EXCEPTION_NODE)Handler)
{
pRemovedNode = pCurrentNode;
RemoveEntryList(&pCurrentNode->ListEntry);
bHandlerExist = TRUE;
break;
}
}
RtlLeaveCriticalSection(&RtlpCalloutEntryLock);
if (bHandlerExist)
{
RtlFreeHeap(GetProcessHeap(), 0, (LPVOID)pRemovedNode);
return 1;
}
return 0;
}
int WINAPI RtlCallVectoredExceptionHanlers(
PEXCEPTION_RECORD pExcptRec,
PCONTEXT pContext)
{
EXCEPTION_POINTERS ExceptionPointers;
if (RtlpCalloutEntryList.Flink == &RtlpCalloutEntryList)
{
return 0;
}
else
{
PVECTORED_EXCEPTION_NODE pCurrentNode;
BYTE retValue = 0;
ExceptionPointers.ExceptionRecord = pExcptRec;
ExceptionPointers.ContextRecord = pContext;
RtlEnterCriticalSection(&RtlpCalloutEntryLock);
for ((PLIST_ENTRY)pCurrentNode = RtlpCalloutEntryList.Flink;
(PLIST_ENTRY)pCurrentNode != &RtlpCalloutEntryList;
(PLIST_ENTRY)pCurrentNode = pCurrentNode->ListEntry.Flink)
{
LONG disposition = ((PVECTORED_EXCEPTION_HANDLER)(
RtlDecodePointer(pCurrentNode->pfnHandler)))(&ExceptionPointers);
if (disposition == EXCEPTION_CONTINUE_EXECUTION)
{
retValue = 1;
break;
}
}
RtlLeaveCriticalSection(&RtlpCalloutEntryLock);
return retValue;
}
}